本例子实现的是将音视频分离,例如将封装格式为 FLV、MKV、MP4、AVI 等封装格式的文件,将音频、视频分离开来。
大致的解封装流程:
1.首先要对解复用器进行初始化。
2.其次将输入的封装格式文件给到解复用器内。
3.最后利用解封装对 Container 进行解封装。
根据流程可以推到出大致的代码流程:
首先对输入文件(Container 文件)、输出文件(Video/Audio 进行处理),方便后面的使用;
其次打开输入文件,并分配 Format Context,从输入文件中得到流信息
之后打开视频、音频编码器 Context,针对视频数据,分配图像 image。
分配 frame 结构,初始化 packet,从输入文件中读取 frame 信息,并之后进行解码 packet。
最后释放各种分配的数据信息。
下面讨论如何利用ffmpeg提供的API函数进行多媒体文件的解封装(demux)过程。
在讲解之前,我们先复习一些基本的多媒体文件知识。
容器格式:
不管是音频文件还是视频格式的文件,都是一个多媒体的容器,即container,比如常见的视频容器格式有avi、mp4、mkv、flv、rm/rmvb、mov、ts、vob、dat,音频容器格式有MP3、WAV、AAC、APE,FLAC等等,它容纳了视频、音频、字幕(subtitle)等一个或多个基本流数据,有的甚至一个容器中存放有多个视频、音频以及字幕。
压缩格式:
对视频、音频数据的基本流进行的压缩方式就是音视频的压缩格式。
常见的视频压缩格式如mpeg2、mpeg4、H264、VC1、Rm/Rmvb,常见音频压缩格式如MPA、AAC、AC3、DTS。注意这里的部分名字和上面的一样,但意义不同,上面是封装格式,这里是压缩格式。
为什么要压缩呢?因为不压缩的话,要存储图像或声音就需要非常多的空间,比如mpeg2压缩比能达到25:1左右,而H264甚至能达到102:1的惊人程度!
ES:也就是ElementaryStream,也称为基本流、组件流等称呼,就是单独的一路视频、一条音频、一个subtitle字幕或者单个的附加数据。显然常见的多媒体文件一个都有一个视频ES、音频ES,有的也含有多个视频ES和音频ES以及subtitleES。比如蓝光原版的TS一般都含有多个音轨ES和字幕ES,但不是所有有字幕都有字幕ES,可能字幕已经内嵌进视频,这样的字幕其实成了视频的一部分。
Demux:在播放时,需要把这些视音频以及字幕等基本流分离出来,这个过程就叫Demux,或者解封装,也称为解复用。分离出来的各个基本流(ES)分别送给视频解码器、音频解码器等解码后才能得到图像声音。:
Remux:当然Demux反过来把基本的音频、视频、字幕等组合成一个完整的多媒体就是Remux或者封装,也称为复用。比如很多电影网站的音视频压制的人就需要先做Demux,分离成ES,在加入必要的中文字幕和音轨后、重新封装。所有的转码工具也都必须有Remux和重新Demux的过程。复用与解复用的概念对于熟悉DVB行业的读者来说应该比较清楚。
PTS:也就是显示时间戳,指图像或者声音在解码后应该显示或者发声的时间点。音视频不是一解码出来就播出来,否则就乱了,性能好的解码器播放的快,差的播放的慢,并且视频和音频也对不上号。所有这些都是靠PTS来同步的。至于DTS解码时间戳在现在相对以前较大解码内存缓冲下,显得不那么重要了。
有了这些基本的多媒体知识,我们就可以继续讲解如何利用ffmpeg来进行Demux这个过程。
首先介绍一下主要的几个API函数:
Int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options);
这个函数用于打开多媒体文件,并读取相关文件头信息。
void avformat_close_input(AVFormatContext **ps);
这个函数用于关闭上面打开的多媒体文件,释放相关资源。
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary**options);
这个函数通过注册的文件格式解析器读取文件的取各种信息,比如播放持续时间、音视频压缩格式、音轨信息、字幕信息、帧率、采样率等等。
int av_read_frame(AVFormatContext*s, AVPacket *pkt);
这个函数对于Demux过程是最重要的一个函数,它从文件中读取一帧视频、一帧或多帧音频、字幕等ES数据包,除了数据本身之外,还包括PTS、持续时间、参考帧等重要信息。
void av_free_packet(AVPacket *pkt);
这个函数用于释放ES数据包,与上面的函数成对使用。
有了这些函数和上面的基本知识,下面我们来实现一个简单的Demux框架实例。
这个实例的功能是把多媒体文件中的音视频ES数据抽出来分别写入不同文件。
我们为了简单,这里不处理返回错误,在实际项目中自己添加错误处理机制。
本文用最简单最原始的方式把ffmpeg解封装的基本框架讲解清楚。
extern "C"{ #include} static const char *media_file = "test_media.mp4"; int main(void) { int i, vid_idx, aud_idx; FILE *fp_vides = NULL, *fp_audes = NULL; AVFormatContext *pFormatCtx = NULL; AVPacket pkt; av_register_all(); avformat_open_input(&pFormatCtx, media_file, NULL, NULL); avformat_find_stream_info(pFormatCtx, NULL); fp_vides = fopen("vid_es.dat", "wb"); fp_audes = fopen("aud_es.dat", "wb"); // 1, handle stream info for (i=0; i nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO) vid_idx = i; else if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_AUDIO) aud_idx = i; else ;//such as subtitile } while (av_read_frame(pFormatCtx, &pkt) >= 0) { // 2, handle pkt data if (pkt.stream_index == vid_idx) fwrite(pkt.data, pkt.size, 1, fp_vides); else if (pkt.stream_index == aud_idx) fwrite(pkt.data, pkt.size, 1, fp_audes); else ;// such as subtitile av_free_packet(&pkt); } fclose(fp_vides); fclose(fp_audes); avformat_close_input(&pFormatCtx); return 0; }