ffmpeg视频播放过程文档格式.docx
《ffmpeg视频播放过程文档格式.docx》由会员分享,可在线阅读,更多相关《ffmpeg视频播放过程文档格式.docx(16页珍藏版)》请在冰点文库上搜索。
解析文件时会将音/视频帧读入到packet中
打开文件
接下来我们打开一个视频文件。
av_register_all();
av_register_all定义在libavformat里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。
if(avformat_open_input(&
pFormatCtx,argv[1],NULL,NULL)!
=0)
return-1;
使用新的APIavformat_open_input来打开一个文件,第一个参数是一个AVFormatContext指针变量的地址,它会根据打开的文件信息填充AVFormatContext,需要注意的是,此处的pFormatContext必须为NULL或由avformat_alloc_context分配得到,这也是上一节中将其初始化为NULL的原因,否则此函数调用会出问题。
第二个参数是打开的文件名,通过argv[1]指定,也就是命令行的第一个参数。
后两个参数分别用于指定特定的输入格式(AVInputFormat)以及指定文件打开额外参数的AVDictionary结构,这里均留作NULL。
if(avformat_find_stream_info(pFormatCtx,NULL)<
0)
av_dump_format(pFormatCtx,-1,argv[1],0);
avformat_open_input函数只是读文件头,并不会填充流信息,因此我们需要接下来调用avformat_find_stream_info获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->
streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。
最后调用一个帮助函数av_dump_format,输出文件的信息,也就是我们在使用ffmpeg时能看到的文件详细信息。
第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。
最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。
现在pFormatCtx->
streams中已经有所有流了,因此现在我们遍历它找到第一条视频流:
videoStream=-1;
for(i=0;
i<
pFormatCtx->
nb_streams;
i++)
if(pFormatCtx->
streams[i]->
codec->
codec_type==AVMEDIA_TYPE_VIDEO){
videoStream=i;
break;
}
if(videoStream==-1)
codec_type的宏定义已经由以前的CODEC_TYPE_VIDEO改为AVMEDIA_TYPE_VIDEO了。
接下来我们通过这条videostream的编解码信息打开相应的解码器:
pCodecCtx=pFormatCtx->
streams[videoStream]->
codec;
pCodec=avcodec_find_decoder(pCodecCtx->
codec_id);
if(pCodec==NULL)
if(avcodec_open2(pCodecCtx,pCodec,NULL)<
分配图像缓存
接下来我们准备给即将解码的图片分配内存空间。
pFrame=avcodec_alloc_frame();
if(pFrame==NULL)
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
调用avcodec_alloc_frame分配帧,因为最后我们会将图像写成24-bitsRGB的PPM文件,因此这里需要两个AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据。
numBytes=avpicture_get_size(PIX_FMT_RGB24,pCodecCtx->
width,
pCodecCtx->
height);
这里调用avpicture_get_size,根据pCodecCtx中原始图像的宽高计算RGB24格式的图像需要占用的空间大小,这是为了之后给pFrameRGB分配空间。
buffer=av_malloc(numBytes);
avpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,
pCodecCtx->
width,pCodecCtx->
接着上面的,首先是用av_malloc分配上面计算大小的内存空间,然后调用avpicture_fill将pFrameRGB跟buffer指向的内存关联起来。
获取图像
OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。
i=0;
while(av_read_frame(pFormatCtx,&
packet)>
=0){
if(packet.stream_index==videoStream){
avcodec_decode_video2(pCodecCtx,pFrame,&
frameFinished,&
packet);
if(frameFinished){
structSwsContext*img_convert_ctx=NULL;
img_convert_ctx=
sws_getCachedContext(img_convert_ctx,pCodecCtx->
pCodecCtx->
height,pCodecCtx->
pix_fmt,
height,
PIX_FMT_RGB24,SWS_BICUBIC,
NULL,NULL,NULL);
if(!
img_convert_ctx){
fprintf(stderr,"
Cannotinitializeswsconversioncontext\n"
);
exit
(1);
sws_scale(img_convert_ctx,(constuint8_t*const*)pFrame->
data,
pFrame->
linesize,0,pCodecCtx->
height,pFrameRGB->
pFrameRGB->
linesize);
if(i++<
50)
SaveFrame(pFrameRGB,pCodecCtx->
height,i);
av_free_packet(&
av_read_frame从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用avcodec_decode_video2对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置frameFinished,如果已经得到下一帧图像则设置frameFinished非零,否则为零。
所以这里我们判断frameFinished是否为零来确定pFrame中是否已经得到解码的图像。
注意在每次处理完后需要调用av_free_packet释放读取的packet。
解码得到图像后,很有可能不是我们想要的RGB24格式,因此需要使用swscale来做转换,调用sws_getCachedContext得到转换上下文,使用sws_scale将图形从解码后的格式转换为RGB24,最后将前50帧写人ppm文件。
最后释放图像以及关闭文件:
av_free(buffer);
av_free(pFrameRGB);
av_free(pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&
pFormatCtx);
return0;
}
staticvoidSaveFrame(AVFrame*pFrame,intwidth,intheight,intiFrame)
FILE*pFile;
charszFilename[32];
inty;
sprintf(szFilename,"
frame%d.ppm"
iFrame);
pFile=fopen(szFilename,"
wb"
pFile)
return;
fprintf(pFile,"
P6\n%d%d\n255\n"
width,height);
for(y=0;
y<
height;
y++)
fwrite(pFrame->
data[0]+y*pFrame->
linesize[0],1,width*3,pFile);
fclose(pFile);
重点分析AVCodec/AVCodecContext/MsrleContext这几个数据结构,这几个数据结构定义了编解码
器的核心架构,相当于Directshow中的各种音视频解码器decoder。
typedefstructAVCodec
{
constchar*name;
//标示Codec的名字,比如,"
msrle"
"
truespeech"
等。
enumCodecTypetype;
//标示Codec的类型,有Video,Audio,Data等类型
enumCodecIDid;
//标示Codec的ID,有CODEC_ID_MSRLE,CODEC_ID_TRUESPEECH等
intpriv_data_size;
//标示具体的Codec对应的Context的大小,在本例中是MsrleContext//或TSContext的大小。
int(*init)(AVCodecContext*);
//标示Codec对外提供的操作
int(*encode)(AVCodecContext*,uint8_t*buf,intbuf_size,void*data);
int(*close)(AVCodecContext*);
int(*decode)(AVCodecContext*,void*outdata,int*outdata_size,uint8_t*buf,intbuf_size);
intcapabilities;
//标示Codec的能力,在瘦身后的ffplay中没太大作用,可忽略
structAVCodec*next;
//用于把所有Codec串成一个链表,便于遍历
}AVCodec;
AVCodec是类似COM接口的数据结构,表示音视频编解码器,着重于功能函数,一种媒体类型对应一个
AVCodec结构,在程序运行时有多个实例。
next变量用于把所有支持的编解码器连接成链表,便于遍历查找;
id
确定了唯一编解码器;
priv_data_size表示具体的Codec对应的Context结构大小,比如MsrleContext或
TSContext,这些具体的结够定义散落于各个.c文件中,为避免太多的ifelse类语句判断类型再计算大小,这里
就直接指明大小,因为这是一个编译时静态确定的字段,所以放在AVCodec而不是AVCodecContext中。
typedefstructAVCodecContext
{
intbit_rate;
intframe_number;
unsignedchar*extradata;
//Codec的私有数据,对Audio是WAVEFORMATEX结构扩展字节。
intextradata_size;
//对Video是BITMAPINFOHEADER后的扩展字节
intwidth,height;
//此逻辑段仅针对视频
enumPixelFormatpix_fmt;
intsample_rate;
//此逻辑段仅针对音频
intchannels;
intbits_per_sample;
intblock_align;
structAVCodec*codec;
//指向当前AVCodec的指针,
void*priv_data;
//指向当前具体编解码器Codec的上下文Context。
enumCodecTypecodec_type;
//seeCODEC_TYPE_xxx
enumCodecIDcodec_id;
//seeCODEC_ID_xxx
int(*get_buffer)(structAVCodecContext*c,AVFrame*pic);
void(*release_buffer)(structAVCodecContext*c,AVFrame*pic);
int(*reget_buffer)(structAVCodecContext*c,AVFrame*pic);
intinternal_buffer_count;
void*internal_buffer;
structAVPaletteControl*palctrl;
}AVCodecContext;
AVCodecContext结构表示程序运行的当前Codec使用的上下文,着重于所有Codec共有的属性(并且是在程
序运行时才能确定其值)和关联其他结构的字段。
extradata和extradata_size两个字段表述了相应Codec使用的私
有数据,对Codec全局有效,通常是一些标志信息;
codec字段关联相应的编解码器;
priv_data字段关联各个具
体编解码器独有的属性上下文,和AVCodec结构中的priv_data_size配对使用。
typedefstructMsrleContext
AVCodecContext*avctx;
AVFrameframe;
unsignedchar*buf;
intsize;
}MsrleContext;
MsrleContext结构着重于RLE行程长度压缩算法独有的属性值和关联AVCodecContext的avctx字段。
因为
RLE行程长度算法足够简单,属性值相对较少。
接着来重点分析AVInputFormat/AVFormatContext/AVIContext这几个数据结构,这几个数据结构定义了识别文件容器格式的核心架构,相当于Directshow中的各种解复用demuxer。
typedefstructAVInputFormat
//标示具体的文件容器格式对应的Context的大小,在本例中是AVIContext
int(*read_probe)(AVProbeData*);
int(*read_header)(structAVFormatContext*,AVFormatParameters*ap);
int(*read_packet)(structAVFormatContext*,AVPacket*pkt);
int(*read_close)(structAVFormatContext*);
constchar*extensions;
//文件扩展名
structAVInputFormat*next;
}AVInputFormat;
AVInputFormat是类似COM接口的数据结构,表示输入文件容器格式,着重于功能函数,一种文件容器格
式对应一个AVInputFormat结构,在程序运行时有多个实例。
next变量用于把所有支持的输入文件容器格式连接
成链表,便于遍历查找;
priv_data_size标示具体的文件容器格式对应的Context的大小,在本例中是AVIContext,
这些具体的结够定义散落于各个.c文件中,为避免太多的ifelse类语句判断类型再计算大小,这里就直接指明大小,因为这是一个编译时静态确定的字段,所以放在AVInputFormat而不是AVFormatContext中。
typedefstructAVFormatContext//formatI/Ocontext
structAVInputFormat*iformat;
//指向具体的文件容器格式的上下文Context,在本例中是AVIContext
ByteIOContextpb;
//广泛意义的输入文件
intnb_streams;
AVStream*streams[MAX_STREAMS];
}AVFormatContext;
AVFormatContext结构表示程序运行的当前文件容器格式使用的上下文,着重于所有文件容器共有的属性(并且是在程序运行时才能确定其值)和关联其他结构的字段。
iformat字段关联相应的文件容器格式;
pb关联广义的
输入文件;
streams关联音视频流;
priv_data字段关联各个具体文件容器独有的属性上下文,和priv_data_size配
对使用。
typedefstructAVIContext
int64_triff_end;
int64_tmovi_end;
offset_tmovi_list;
intnon_interleaved;
intstream_index_2;
//为了和AVPacket中的stream_index相区别,添加后缀标记。
}AVIContext;
AVIContext定义了AVI中流的一些属性,其中stream_index_2定义了当前应该读取流的索引。
接着我们来重点分析URLProtocol/URLContext(ByteIOContext)/FILE(Socket)这几个数据结构,这几个数据结
构定义了读取文件的核心架构,相当于Directshow中的文件源filesourcefilter。
typedefstructURLProtocol
//便于人性化的识别理解
int(*url_open)(URLContext*h,constchar*filename,intflags);
int(*url_read)(URLContext*h,unsignedchar*buf,intsize);
int(*url_write)(URLContext*h,unsignedchar*buf,intsize);
offset_t(*url_seek)(URLContext*h,offset_tpos,intwhence);
int(*url_close)(URLContext*h);
structURLProtocol*next;
}URLProtocol;
URLProtocol是类似COM接口的数据结构,表示广义的输入文件,着重于功能函数,一种广义的输入文件
对应一个URLProtocol结构,比如file,pipe,tcp等等,但瘦身后的ffplay只支持file一种输入文件。
next变量
用于把所有支持的广义的输入文件连接成链表,便于遍历查找。
typedefstructURLContext
structURLProtocol*prot;
intflags;
intmax_packet_size;
//ifnonzero,thestreamispacketizedwiththismaxpacketsize
//文件句柄fd,网络通信Scoket等
charfilename[1];
//specifiedfilename
}URLContext;
URLCon