音视频处理之FFmpeg封装格式20180510

一、FFMPEG的封装格式转换器(无编解码)

1.封装格式转换

所谓的封装格式转换,就是在AVI,FLV,MKV,MP4这些格式之间转换(对应.avi,.flv,.mkv,.mp4文件)。

需要注意的是,本程序并不进行视音频的编码和解码工作。而是直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。

本程序的工作原理如下图1所示:

 音视频处理之FFmpeg封装格式20180510

由图可见,本程序并不进行视频和音频的编解码工作,因此本程序和普通的转码软件相比,有以下两个特点:

处理速度极快。视音频编解码算法十分复杂,占据了转码的绝大部分时间。因为不需要进行视音频的编码和解码,所以节约了大量的时间。

视音频质量无损。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

 

2.基于FFmpeg的Remuxer的流程图

下面附上基于FFmpeg的Remuxer的流程图。图2中使用浅红色标出了关键的数据结构,浅蓝色标出了输出视频数据的函数。

可见成个程序包含了对两个文件的处理:读取输入文件(位于左边)和写入输出文件(位于右边)。中间使用了一个avcodec_copy_context()拷贝输入的AVCodecContext到输出的AVCodecContext。

 音视频处理之FFmpeg封装格式20180510

简单介绍一下流程中关键函数的意义:

输入文件操作:

avformat_open_input():打开输入文件,初始化输入视频码流的AVFormatContext。

av_read_frame():从输入文件中读取一个AVPacket。

 

输出文件操作:

avformat_alloc_output_context2():初始化输出视频码流的AVFormatContext。

avformat_new_stream():创建输出码流的AVStream。

avcodec_copy_context():拷贝输入视频码流的AVCodecContex的数值t到输出视频的AVCodecContext。

avio_open():打开输出文件。

avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

av_interleaved_write_frame():将AVPacket(存储视频压缩码流数据)写入文件。

av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

 

二、FFmpegRemuxer代码

基于FFmpeg的封装格式转换器,取了个名字称为FFmpegRemuxer

主要是FFmpegRemuxer.cpp文件,代码如下(基本上每一行都有注释):

音视频处理之FFmpeg封装格式20180510 FFmpegRemuxer.cpp

具体代码见github:

https://github.com/fengweiyu/FFmpegFormat/FFmpegRemuxer

 

三、FFmpeg的封装格式处理:视音频复用器(muxer)

1.封装格式处理

视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。

如图3所示。在这个过程中并不涉及到编码和解码。

 音视频处理之FFmpeg封装格式20180510

2.基于FFmpeg的muxer的流程图

程序的流程如下图4所示。从流程图中可以看出,一共初始化了3个AVFormatContext,其中2个用于输入,1个用于输出。3个AVFormatContext初始化之后,通过avcodec_copy_context()函数可以将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体。

然后分别调用视频输入流和音频输入流的av_read_frame(),从视频输入流中取出视频的AVPacket,音频输入流中取出音频的AVPacket,分别将取出的AVPacket写入到输出文件中即可。

其间用到了一个不太常见的函数av_compare_ts(),是比较时间戳用的。通过该函数可以决定该写入视频还是音频。

 音视频处理之FFmpeg封装格式20180510

本文介绍的视音频复用器,输入的视频不一定是H.264裸流文件,音频也不一定是纯音频文件。可以选择两个封装过的视音频文件作为输入。程序会从视频输入文件中“挑”出视频流,音频输入文件中“挑”出音频流,再将“挑选”出来的视音频流复用起来。

PS1:对于某些封装格式(例如MP4/FLV/MKV等)中的H.264,需要用到名称为“h264_mp4toannexb”的bitstream filter。

PS2:对于某些封装格式(例如MP4/FLV/MKV等)中的AAC,需要用到名称为“aac_adtstoasc”的bitstream filter。

 

简单介绍一下流程中各个重要函数的意义:

avformat_open_input():打开输入文件。

avcodec_copy_context():赋值AVCodecContext的参数。

avformat_alloc_output_context2():初始化输出文件。

avio_open():打开输出文件。

avformat_write_header():写入文件头。

av_compare_ts():比较时间戳,决定写入视频还是写入音频。这个函数相对要少见一些。

av_read_frame():从输入文件读取一个AVPacket。

av_interleaved_write_frame():写入一个AVPacket到输出文件。

av_write_trailer():写入文件尾。

 

3.优化为可以从内存中读取音视频数据

打开文件的函数是avformat_open_input(),直接将文件路径或者流媒体URL的字符串传递给该函数就可以了。

但其是否支持从内存中读取数据呢?

 

分析ffmpeg的源代码,发现其竟然是可以从内存中读取数据的,代码很简单,如下所示:

ptInFormatContext = avformat_alloc_context(); 

pbIoBuf = (unsigned char *)av_malloc(IO_BUFFER_SIZE); 

ptAVIO = avio_alloc_context(pbIoBuf, IO_BUFFER_SIZE, 0, NULL, FillIoBuffer, NULL, NULL);

ptInFormatContext->pb = ptAVIO;

 

ptInputFormat = av_find_input_format("h264");//得到ptInputFormat以便后面打开使用

if ((iRet = avformat_open_input(&ptInFormatContext, "", ptInputFormat, NULL)) < 0)

{

    printf("Could not open input file\r\n");

}

else

{

}

关键要在avformat_open_input()之前初始化一个AVIOContext,而且将原本的AVFormatContext的指针pb(AVIOContext类型)指向这个自行初始化AVIOContext。

当自行指定了AVIOContext之后,avformat_open_input()里面的URL参数就不起作用了。示例代码开辟了一块空间iobuffer作为AVIOContext的缓存。

FillIoBuffer则是将数据读取至iobuffer的回调函数。FillIoBuffer()形式(参数,返回值)是固定的,是一个回调函数,如下所示(只是个例子,具体怎么读取数据可以自行设计)。

示例中回调函数将文件中的内容通过fread()读入内存。

int FillIoBuffer(void *opaque, unsigned char *o_pbBuf, int i_iMaxSize) 

{

    int iRet=-1;

    if (!feof(g_fileH264))

    { 

        iRet = fread(o_pbBuf, 1, i_iMaxSize, g_fileH264); 

    } 

    else

    { 

    } 

    return iRet; 

整体结构大致如下:

FILE *fp_open; 

 

int fill_iobuffer(void *opaque, uint8_t *buf, int buf_size){ 

... 

 

int main(){ 

    ... 

    fp_open=fopen("test.h264","rb+"); 

    AVFormatContext *ic = NULL; 

    ic = avformat_alloc_context(); 

    unsigned char * iobuffer=(unsigned char *)av_malloc(32768); 

    AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,NULL,fill_iobuffer,NULL,NULL); 

    ic->pb=avio; 

    err = avformat_open_input(&ic, "nothing", NULL, NULL); 

    ...//解码 

 

4.将音视频数据输出到内存

同时再说明一下,和从内存中读取数据类似,ffmpeg也可以将处理后的数据输出到内存。

 

回调函数如下示例,可以将输出到内存的数据写入到文件中。

//写文件的回调函数 

int write_buffer(void *opaque, uint8_t *buf, int buf_size){ 

    if(!feof(fp_write)){ 

        int true_size=fwrite(buf,1,buf_size,fp_write); 

        return true_size; 

    }else{ 

        return -1; 

    } 

}

主函数如下所示:

FILE *fp_write; 

 

int write_buffer(void *opaque, uint8_t *buf, int buf_size){ 

... 

 

main(){ 

    ... 

    fp_write=fopen("src01.h264","wb+"); //输出文件 

    ... 

    AVFormatContext* ofmt_ctx=NULL; 

    avformat_alloc_output_context2(&ofmt_ctx, NULL, "h264", NULL); 

    unsigned char* outbuffer=(unsigned char*)av_malloc(32768); 

 

    AVIOContext *avio_out =avio_alloc_context(outbuffer, 32768,0,NULL,NULL,write_buffer,NULL);   

 

    ofmt_ctx->pb=avio_out;  

    ofmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO; 

    ... 

从上述可以很明显的看到,知道把写回调函数放到avio_alloc_context函数对应的位置就可以了。

 

四、FFmpegMuxer代码

基于FFmpeg的视音频复用器,取了个名字称为FFmpegMuxer

主要是FFmpegMuxer.cpp文件,代码如下(基本上每一行都有注释):

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 

具体代码见github:

https://github.com/fengweiyu/FFmpegFormat/FFmpegMuxer

 

五、参考原文:

https://blog.csdn.net/leixiaohua1020/article/details/25422685

https://blog.csdn.net/leixiaohua1020/article/details/39802913

https://blog.csdn.net/leixiaohua1020/article/details/12980423

https://blog.csdn.net/leixiaohua1020/article/details/39759163