FFMPEG解码流程.docx
《FFMPEG解码流程.docx》由会员分享,可在线阅读,更多相关《FFMPEG解码流程.docx(33页珍藏版)》请在冰豆网上搜索。
FFMPEG解码流程
FFMPEG解码流程
FFMPEG解码流程:
1.注册所有容器格式和CODEC:
av_register_all()
2.打开文件:
av_open_input_file()
3.从文件中提取流信息:
av_find_stream_info()
4.穷举所有的流,查找其中种类为CODEC_TYPE_VIDEO
5.查找对应的解码器:
avcodec_find_decoder()
6.打开编解码器:
avcodec_open()
7.为解码帧分配内存:
avcodec_alloc_frame()
8.不停地从码流中提取出帧数据:
av_read_frame()
9.判断帧的类型,对于视频帧调用:
avcodec_decode_video()
10.解码完后,释放解码器:
avcodec_close()
11.关闭输入文件:
avformat_close_input_file()
主要数据结构:
基本概念:
编解码器、数据帧、媒体流和容器是数字媒体处理系统的四个基本概念。
首先需要统一术语:
容器/文件(Conainer/File):
即特定格式的多媒体文件。
媒体流(Stream):
指时间轴上的一段连续数据,如一段声音数据,一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。
数据帧/数据包(Frame/Packet):
通常,一个媒体流由大量的数据帧组成,对于压缩数据,帧对应着编解码器的最小处理单元。
通常,分属于不同媒体流的数据帧交错复用于容器之中,参见交错。
编解码器:
编解码器以帧为单位实现压缩数据和原始数据之间的相互转换。
在FFMPEG中,使用AVFormatContext、AVStream、AVCodecContext、AVCodec及AVPacket等结构来抽象这些基本要素,它们的关系如上图所示:
AVCodecContext:
这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,如下列出了部分比较重要的域:
typedefstructAVCodecContext{
/**
*一些编解码器需要/可以像使用extradataHuffman表。
*MJPEG:
Huffman表
*RV10其他标志
*MPEG4:
全球头(也可以是在比特流或这里)
*分配的内存应该是FF_INPUT_BUFFER_PADDING_SIZE字节较大
*,比extradata_size避免比特流器,如果它与读prolems。
*extradata按字节的内容必须不依赖于架构或CPU的字节顺序。
*-编码:
设置/分配/释放由libavcodec的。
*-解码:
由用户设置/分配/释放。
*/
uint8_t*extradata;
intextradata_size;
/**
*这是时间的基本单位,在条件(以秒为单位)
*帧时间戳派代表出席了会议。
对于固定fps的内容,
*基应该1/framerate和时间戳的增量应该
*相同的1。
*-编码:
必须由用户设置。
*-解码:
libavcodec的设置。
*/
AVRationaltime_base;
enumCodecIDcodec_id;
/**
*的fourcc(LSB在前,所以“的ABCD”->(“D”<<24)(“C”<<16)(“B”<<8)+“A”)。
*这是用来解决一些编码错误。
*分路器应设置什么是编解码器用于识别领域中。
*如果有分路器等多个领域,在一个容器,然后选择一个
*最大化使用的编解码器有关的信息。
*如果在容器中的编解码器标记字段然后32位大分路器应该
*重新映射到一个表或其他结构的32位编号。
也可选择新
*extra_codec_tag+大小可以添加,但必须证明这是一个明显的优势
*第一。
*-编码:
由用户设置,如果没有则默认基础上codec_id将使用。
*-解码:
由用户设置,将被转换成在初始化libavcodec的大写。
*/
unsignedintcodec_tag;
......
/**
*在解码器的帧重排序缓冲区的大小。
*对于MPEG-2,这是IPB1或0低延时IP。
*-编码:
libavcodec的设置。
*-解码:
libavcodec的设置。
*/
inthas_b_frames;
/**
*每包的字节数,如果常量和已知或0
*用于一些WAV的音频编解码器。
*/
intblock_align;
/**
*从分路器位每个样品/像素(huffyuv需要)。
*-编码:
libavcodec的设置。
*-解码:
由用户设置。
*/
intbits_per_coded_sample;
.....
}AVCodecContext;
如果是单纯使用libavcodec,这部分信息需要调用者进行初始化;如果是使用整个FFMPEG库,这部分信息在调用avformat_open_input和avformat_find_stream_info的过程中根据文件的头信息及媒体流内的头部信息完成初始化。
其中几个主要域的释义如下:
extradata/extradata_size:
这个buffer中存放了解码器可能会用到的额外信息,在av_read_frame中填充。
一般来说,首先,某种具体格式的demuxer在读取格式头信息的时候会填充extradata,其次,如果demuxer没有做这个事情,比如可能在头部压根儿就没有相关的编解码信息,则相应的parser会继续从已经解复用出来的媒体流中继续寻找。
在没有找到任何额外信息的情况下,这个buffer指针为空。
time_base:
width/height:
视频的宽和高。
sample_rate/channels:
音频的采样率和信道数目。
sample_fmt:
音频的原始采样格式。
codec_name/codec_type/codec_id/codec_tag:
编解码器的信息。
AVStrea
该结构体描述一个媒体流,定义如下:
typedefstructAVStream{
intindex;
AVCodecContext*codec;
/**
*流的实时帧率基地。
*这是所有时间戳可以最低帧率
*准确代表(它是所有的最小公倍数
*流的帧率)。
请注意,这个值只是一个猜测!
*例如,如果时间基数为1/90000和所有帧
*约3600或1800计时器刻度,,然后r_frame_rate将是50/1。
*/
AVRationalr_frame_rate;
/**
*这是时间的基本单位,在条件(以秒为单位)
*帧时间戳派代表出席了会议。
对于固定fps的内容,
*时基应该是1/framerate的时间戳的增量应为1。
*/
AVRationaltime_base;
......
/**
*解码流量的第一帧,在流量时-base分。
*如果你是绝对100%的把握,设定值
*它真的是第一帧点。
*这可能是未定义(AV_NOPTS_VALUE)的。
*@注意的业余头不弱者受制与正确的START_TIME的业余
*分路器必须不设定此。
*/
int64_tstart_time;
/**
*解码:
时间流流时基。
*如果源文件中没有指定的时间,但不指定
*比特率,这个值将被从码率和文件大小的估计。
*/
int64_tduration;
#ifLIBAVFORMAT_VERSION_INT<(53<<16)
charlanguage[4];
#endif
/*流信息*/
int64_ttimestamp;
#ifLIBAVFORMAT_VERSION_INT<(53<<16)
chartitle[512];
charauthor[512];
charcopyright[512];
charcomment[512];
charalbum[512];
intyear;
inttrack;
chargenre[32];
#endif
intctx_flags;
int64_tdata_offset;
intindex_built;
intmux_rate;
unsignedintpacket_size;
intpreload;
intmax_delay;
#defineAVFMT_NOOUTPUTLOOP-1
#defineAVFMT_INFINITEOUTPUTLOOP0
intloop_output;
intflags;
#defineAVFMT_FLAG_GENPTS0x0001///<生成失踪分,即使它需要解析未来框架。
#defineAVFMT_FLAG_IGNIDX0x0002///<忽略指数。
#defineAVFMT_FLAG_NONBLOCK0x0004///<从输入中读取数据包时,不要阻止。
#defineAVFMT_FLAG_IGNDTS0x0008///<忽略帧的DTS包含DTS与PTS
#defineAVFMT_FLAG_NOFILLIN0x0010///<不要从任何其他值推断值,只是返回存储在容器中
#defineAVFMT_FLAG_NOPARSE0x0020///<不要使用AVParsers,你还必须设置为FILLIN帧代码的工作,没有解析AVFMT_FLAG_NOFILLIN->无帧。
也在寻求框架不能工作,如果找到帧边界的解析已被禁用
#defineAVFMT_FLAG_RTP_HINT0x0040///<暗示到输出文件添加的RTP
intloop_input;
CODEC_ID_MPEG1VIDEO,
CODEC_ID_MPEG2VIDEO,/// CODEC_ID_MPEG2VIDEO_XVMC,
CODEC_ID_H261,
CODEC_ID_H263,
...
};
通常,如果某种媒体格式具备完备而正确的头信息,调用avformat_open_input即可以得到这两个参数,但若是因某种原因avformat_open_input无法获取它们,这一任务将由avformat_find_stream_info完成。
其次还要获取各媒体流对应编解码器的时间基准。
此外,对于音频编解码器,还需要得到:
采样率,
声道数,
位宽,
帧长度(对于某些编解码器是必要的),
对于视频编解码器,则是:
图像大小,
色彩空间及格式,
av_read_frame
intav_read_frame(AVFormatContext*s,AVPacket*pkt);
这个函数用于从多媒体文件或多媒体流中读取媒体数据,获取的数据由AVPacket结构pkt来存放。
对于音频数据,如果是固定比特率,则pkt中装载着一个或多个音频帧;如果是可变比特率,则pkt中装载有一个音频帧。
对于视频数据,pkt中装载有一个视频帧。
需要注意的是:
再次调用本函数之前,必须使用av_free_packet释放pkt所占用的资源。
通过pkt→stream_index可以查到获取的媒体数据的类型,从而将数据送交相应的解码器进行后续处理。
av_seek_frame
intav_seek_frame(AVFormatContext*s,intstream_index,int64_ttimestamp,intflags);
这个函数通过改变媒体文件的读写指针来实现对媒体文件的随机访问,支持以下三种方式:
基于时间的随机访问:
具体而言就是将媒体文件读写指针定位到某个给定的时间点上,则之后调用av_read_frame时能够读到时间标签等于给定时间点的媒体数据,通常用于实现媒体播放器的快进、快退等功能。
基于文件偏移的随机访问:
相当于普通文件的seek函数,timestamp也成为文件的偏移量。
基于帧号的随机访问:
timestamp为要访问的媒体数据的帧号。
关于参数:
s:
是个AVFormatContext指针,就是avformat_open_input返回的那个结构。
stream_index:
指定媒体流,如果是基于时间的随机访问,则第三个参数timestamp将以此媒体流的时间基准为单位;如果设为负数,则相当于不指定具体的媒体流,FFMPEG会按照特定的算法寻找缺省的媒体流,此时,timestamp的单位为AV_TIME_BASE(微秒)。
timestamp:
时间标签,单位取决于其他参数。
flags:
定位方式,AVSEEK_FLAG_BYTE表示基于字节偏移,AVSEEK_FLAG_FRAME表示基于帧号,其它表示基于时间。
av_close_input_file:
voidav_close_input_file(AVFormatContext*s);
关闭一个媒体文件:
释放资源,关闭物理IO。
avcodec_find_decoder:
AVCodec*avcodec_find_decoder(enumCodecIDid);
AVCodec*avcodec_find_decoder_by_name(constchar*name);
根据给定的codecid或解码器名称从系统中搜寻并返回一个AVCodec结构的指针。
avcodec_open:
intavcodec_open(AVCodecContext*avctx,AVCodec*codec);
此函数根据输入的AVCodec指针具体化AVCodecContext结构。
在调用该函数之前,需要首先调用avcodec_alloc_context分配一个AVCodecContext结构,或调用avformat_open_input获取媒体文件中对应媒体流的AVCodecContext结构;此外还需要通过avcodec_find_decoder获取AVCodec结构。
这一函数还将初始化对应的解码器。
avcodec_decode_video2
intavcodec_decode_video2(AVCodecContext*avctx,AVFrame*picture,int*got_picture_ptr,AVPacket*avpkt);
解码一个视频帧。
got_picture_ptr指示是否有解码数据输出。
输入数据在AVPacket结构中,输出数据在AVFrame结构中。
AVFrame是定义在avcodec.h中的一个数据结构:
typedefstructAVFrame{
FF_COMMON_FRAME
}AVFrame;
FF_COMMON_FRAME定义了诸多数据域,大部分由FFMpeg内部使用,对于用户来说,比较重要的主要包括:
#defineFF_COMMON_FRAME\
......
uint8_t*data[4];\
intlinesize[4];\
intkey_frame;\
intpict_type;\
int64_tpts;\
intreference;\
......
FFMpeg内部以planar的方式存储原始图像数据,即将图像像素分为多个平面(R/G/B或Y/U/V),data数组内的指针分别指向四个像素平面的起始位置,linesize数组则存放各个存贮各个平面的缓冲区的行宽:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++data[0]->#################################++++++++++++
++++++++++++###########picturedata##########++++++++++++
........................
++++++++++++#################################++++++++++++
|<-------------------line_size[0]---------------------->|
此外,key_frame标识该图像是否是关键帧;pict_type表示该图像的编码类型:
I
(1)/P
(2)/B(3)……;pts是以time_base为单位的时间标签,对于部分解码器如H.261、H.263和MPEG4,可以从头信息中获取;reference表示该图像是否被用作参考。
avcodec_decode_audio4
intavcodec_decode_audio4(AVCodecContext*avctx,AVFrame*frame,int*got_frame_ptr,AVPacket*avpkt);
解码一个音频帧。
输入数据在AVPacket结构中,输出数据在frame中,got_frame_ptr表示是否有数据输出。
avcodec_close
intavcodec_close(AVCodecContext*avctx);
关闭解码器,释放avcodec_open中分配的资源。
测试程序
#include
#include
#include
#include
#include"libavutil/avstring.h"
#include"libavformat/avformat.h"
#include"libavdevice/avdevice.h"
#include"libavcodec/opt.h"
#include"libswscale/swscale.h"
#defineDECODED_AUDIO_BUFFER_SIZE192000
structoptions
{
intstreamId;
intframes;
intnodec;
intbplay;
intthread_count;
int64_tlstart;
charfinput[256];
charfoutput1[256];
charfoutput2[256];
};
intparse_options(structoptions*opts,intargc,char**argv)
{
intoptidx;
char*optstr;
if(argc<2)return-1;
opts->streamId=-1;
opts->lstart=-1;
opts->frames=-1;
opts->foutput1[0]=0;
opts->foutput2[0]=0;
opts->nodec=0;
opts->bplay=0;
opts->thread_count=0;
strcpy(opts->finput,argv[1]);
optidx=2;
while(optidx {
optstr=argv[optidx++];
if(*optstr++!
='-')return-1;
switch(*optstr++)
{