目录

FFmpeg系列四-mp4音视频流分离

FFmpeg系列(四)—— mp4音视频流分离

文章目录

1、介绍

本文将介绍如何将mp4文件里的音视频分离出来,输出成h264和aac格式文件。整个代码逻辑相对比较简单,main函数一开始做一些基本的初始化,然后在死循环里不断读取音视频流,如果是音频则将数据写入aac文件,如果是视频则写入h264文件,直到音视频帧读取完成。

需要注意的是,在写入音频帧时需要加如adts音频头,格式是固定的,代码如下:

2、完整代码

#ifdef __cplusplus
extern "C"
{
#endif
#include <stdio.h>
#include "libavutil/log.h"
#include "libavformat/avformat.h"
#ifdef __cplusplus
}
#endif

// 添加音频头,固定格式
int adts_header(char *const p_adts_header, const int data_length,
                const int profile, const int samplerate, const int channels)
{
    int sampling_index   = 3;               // 默认使用48000hz
    int adtsLen          = data_length + 7; // adts长度等于数据长度+7字节音频头
    switch (samplerate) {                   // 将采样率转换成索引值
        case 96000: sampling_index = 0; break;
        case 88200: sampling_index = 1; break;
        case 64000: sampling_index = 2; break;
        case 48000: sampling_index = 3; break;
        case 44100: sampling_index = 4; break;
        case 32000: sampling_index = 5; break;
        case 24000: sampling_index = 6; break;
        default:    sampling_index = 3; break;
    }

    p_adts_header[0] = 0xff;                                //syncword:0xfff                          高8bits
    p_adts_header[1] = 0xf0;                                //syncword:0xfff                          低4bits
    p_adts_header[1] |= (0 << 3);                           //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    p_adts_header[1] |= (0 << 1);                           //Layer:0                                 2bits
    p_adts_header[1] |= 1;                                  //protection absent:1                     1bit

    p_adts_header[2] = (profile)<<6;                        //profile:profile                 2bits
    p_adts_header[2] |= (sampling_index & 0x0f)<<2;         //sampling frequency index:sampling_frequency_index  4bits
    p_adts_header[2] |= (0 << 1);                           //private bit:0                   1bit
    p_adts_header[2] |= (channels & 0x04)>>2;               //channel configuration:channels  高1bit

    p_adts_header[3] = (channels & 0x03)<<6;                //channel configuration:channels  低2bits
    p_adts_header[3] |= (0 << 5);                           //original:0                     1bit
    p_adts_header[3] |= (0 << 4);                           //home:0                         1bit
    p_adts_header[3] |= (0 << 3);                           //copyright id bit:0             1bit
    p_adts_header[3] |= (0 << 2);                           //copyright id start:0           1bit
    p_adts_header[3] |= ((adtsLen & 0x1800) >> 11);         //frame length:value             高2bits

    p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);   //frame length:value              中间8bits
    p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);     //frame length:value              低3bits
    p_adts_header[5] |= 0x1f;                               //buffer fullness:0x7ff           高5bits
    p_adts_header[6] = 0xfc;                                //‭buffer fullness:0x7ff           低6bits 11111100‬

    return 0;
}

int main()
{
    char            *mp4_filename     = "source.mp4";               // mp4视频,200kbps.768x320
    char            *h264_filename    = "out.h264";                 // 视频流输出文件
    char            *aac_filename     = "out.aac";                  // 音频流输出文件
    FILE            *aac_fd           = fopen(aac_filename, "wb");  // 打开aac音频输出文件
    FILE            *h264_fd          = fopen(h264_filename, "wb"); // 打开h264视频输出文件
    AVFormatContext *fmt_ctx          = avformat_alloc_context();   // 解封装上下文
    AVPacket        *pkt              = av_packet_alloc();          // 为packet分配空间,用于存放音视频帧数据
    AVBSFContext    *bsf_ctx          = NULL;                       // bit流过滤器上下文
    int             video_index       = -1;                         // 视频流索引
    int             audio_index       = -1;                         // 音频流索引
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");  // 查找h264比特流过滤器

    if (!h264_fd)  { return -1; }  // h264视频输出文件打开失败
    if (!aac_fd)   { return -1; }  // aac音频输出文件打开失败
    if (!fmt_ctx)  { return -1; }  // 解封装上下文创建失败
    if (!pkt)      { return -1; }  // AVPacket创建失败
    if (!bsfilter) { return -1; }  // bit流过滤器创建失败


    int ret = avformat_open_input(&fmt_ctx, mp4_filename, NULL, NULL); // 打开音视频流
    if(ret < 0) {
        avformat_close_input(&fmt_ctx); // 关闭音视频流
        return -1;
    }

    // 获取视频和音频索引
    video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(video_index == -1 || audio_index == -1) {
        avformat_close_input(&fmt_ctx); // 关闭音视频流
        return -1; // 音视频索引获取失败
    }

    ret = av_bsf_alloc(bsfilter, &bsf_ctx); // 为h264比特流过滤器分配空间
    if(ret < 0) {
        avformat_close_input(&fmt_ctx); // 关闭音视频流
        return -1; // 空间分配失败
    }

    // 将编码器的参数拷贝给bit流过滤器
    ret = avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[video_index]->codecpar);
    if(ret < 0) {
        avformat_close_input(&fmt_ctx); // 关闭音视频流
        av_bsf_free(&bsf_ctx);
        return -1; // 参数拷贝失败
    }

    ret = av_bsf_init(bsf_ctx); // 初始化bit流过滤器
    if(ret < 0) {
        avformat_close_input(&fmt_ctx); // 关闭音视频流
        av_bsf_free(&bsf_ctx);
        return -1;
    }

    av_init_packet(pkt);
    while (1)
    {
        ret = av_read_frame(fmt_ctx, pkt);
        if (ret < 0 ) { break; }              // 音视频流读取结束

        if (pkt->stream_index == video_index) // 视频流
        {
            ret = av_bsf_send_packet(bsf_ctx, pkt);
            while (1) {
                ret = av_bsf_receive_packet(bsf_ctx, pkt); // 检索已过滤的数据包
                if(ret != 0) { break; }
                fwrite(pkt->data, 1, pkt->size, h264_fd);  // 写入h264数据
                av_packet_unref(pkt); // 释放buffer
            }
        }
        else if (pkt->stream_index == audio_index)  // 音频流
        {
            char adts_header_buf[7] = {0};  // 每个packet都要添加一个7字节的音频头
            adts_header(adts_header_buf, pkt->size,
                        fmt_ctx->streams[audio_index]->codecpar->profile,     // 编码器bit流限制
                        fmt_ctx->streams[audio_index]->codecpar->sample_rate, // 采样率
                        fmt_ctx->streams[audio_index]->codecpar->channels);   // 通道数
            fwrite(adts_header_buf, 1, 7, aac_fd);   // 写adts header,7字节音频头
            fwrite(pkt->data, 1, pkt->size, aac_fd); // 写adts data,音频数据
            av_packet_unref(pkt); // 释放buffer
        }
        else // 其他流
        {
            av_packet_unref(pkt); // 释放buffer
        }
    }

    printf("Split OK!\n");

    if(h264_fd)
        fclose(h264_fd);
    if(aac_fd)
        fclose(aac_fd);
    if(pkt)
        av_packet_free(&pkt);
    if(fmt_ctx)
        avformat_close_input(&fmt_ctx);

    return 0;
}