2013年(18)
分类: 架构设计与优化
2013-04-23 18:10:57
本文的参考和借鉴:(文章在开篇就声明,它有些过时了)。
所以本文分析的FFmpeg源码为Version0.8.2
我们在网络上看到的“视频”通常都是一种三层数据封装结构:
编码图像和声音形成的裸视频流(如H.264, VP8, 等)和音频流(mp3, AAC等);
用容器格式将这些裸流封装形成文件(FLV,rmvb, mkv等)或流(mpeg2-TS等);
再利用传输协议(如RTSP, RTMP, HTTP, UDP等)将数据通过 互联网,电视线路或无线网络等传输到用户端
(电视机,PC,移动设备等)以收看。
FFmpeg的强大与复杂都体现在对这三层封装数据做了大量的支持和处理,从而使它能完成流或文件输入,
解码显示,转码存储,封装推送等多项工作。
闲言少絮,进入正题。
对于C语言构建的面向过程编程的程序最基本的分析原则是:
去掉无关,分层推进。
FFmpeg最精简的伪码描述可如下图所示:
简而言之就是,打开文件,读取一帧数据 ,处理一帧数据,如此循环直到文件结束。
这个描述是简单而正确的。我们以转码一个文件来对应实际的代码进行分析。
1.main函数(ffmpeg.c)
int main(int argc, char **argv){
/* *_register_all类函数是注册库中所有有效的文件格式和codec。它们只用在main中调用一次即可*/
avcodec_register_all();
/*分析命令行输入参数,进行相关的设置。*/
init_opts();
/* parse options */
parse_options(argc, argv, options, opt_output_file);
/*所有的数据解码,处理,编码等都是在这个函数中实现,它的函数参数可以分成三组,输出,输入以及两者间的映射关系。*/
if (transcode(output_files, nb_output_files, input_files, nb_input_files,
stream_maps, nb_stream_maps) < 0)
ffmpeg_exit(1);
return ffmpeg_exit(0);
}
转码结束后的收尾工作。
2. transcode函数(ffmpeg.c)
/*
* The following code is the main loop of the file converter
*/
static int transcode(AVFormatContext **output_files,int nb_output_files,
InputFile *input_files, int nb_input_files,
StreamMap *stream_maps, int nb_stream_maps)
{
/* 分配输出流的空间 */
ost_table = av_mallocz(sizeof(OutputStream *) * nb_ostreams);
z:是ffmpeg对“malloc”的简单封装,它确保了存储地址的对齐,但不保证存储器的内存泄漏,二次释放或其它的问题。
/* 针对每个输出文件的流,找到对应输入流的索引,以得到正确的流映射 */
for(k=0;k
/* 对每个输出流,计算相应的编码参数 */
for(i=0;i
if (!ost->enc)
ost->enc = avcodec_find_encoder(ost->st->codec->codec_id);
switch(codec->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
/* 计算音频编码参数 */
ost->fifo= av_fifo_alloc(1024);
ost->reformat_pair = MAKE_SFMT_PAIR(AV_SAMPLE_FMT_NONE,AV_SAMPLE_FMT_NONE);
break;
case AVMEDIA_TYPE_VIDEO:
/* 计算视频编码参数 */
choose_pixel_fmt(ost->st, ost->enc);
break;
case AVMEDIA_TYPE_SUBTITLE:
ost->encoding_needed = 1;
ist->decoding_needed = 1;
break;
default:
abort();
break;
}
}
/* 打开每个编码器 open each encoder */
for(i=0;i
ost = ost_table[i];
if (ost->encoding_needed)
{
AVCodec *codec = ost->enc;
AVCodecContext *dec = input_streams[ost->source_index].st->codec;
/* 打开匹配的编码器,并打印出编码参数信息 */
if (avcodec_open2(ost->st->codec, codec, &ost->opts) < 0)
{
...
}
}
}
/* 找到要解码的流,并打开相应的解码器 */
for (i = 0; i < nb_input_streams; i++)
{
ist = &input_streams[i];
/* 只对要解码的输入流才打开相应的解码器 */
if (ist->decoding_needed)
{
/* 打开解码器 */
if (avcodec_open2(ist->st->codec, codec, &ist->opts) < 0)
{
...
}
}
}
/* open files and write file headers */
for(i=0;i
os = output_files[i];
if (avformat_write_header(os, &output_opts[i]) < 0)
{
...
}
}
/* 转码主循环 */
for(; received_sigterm == 0;)
{
/* 从输入的文件或流中读取一帧数据 */
is = input_files[file_index].ctx;
ret= av_read_frame(is, &pkt);
/* 处理一帧数据 */
if (output_packet(ist, ist_index, ost_table, nb_ostreams, &pkt)< 0)
{
...
}
}
/* at the end of stream, we must flush the decoder buffers */
for (i = 0; i < nb_input_streams; i++)
{
ist = &input_streams[i];
if (ist->decoding_needed)
{
output_packet(ist, i, ost_table, nb_ostreams, NULL);
}
}
/* write the trailer if needed and close file */
for(i=0;i
os = output_files[i];
av_write_trailer(os);
}
/* close each encoder */
for(i=0;i
ost = ost_table[i];
if (ost->encoding_needed)
{
av_freep(&ost->st->codec->stats_in);
avcodec_close(ost->st->codec);
}
}
/* close each decoder */
for (i = 0; i < nb_input_streams; i++)
{
ist = &input_streams[i];
if (ist->decoding_needed) {
avcodec_close(ist->st->codec);
}
}
return ret;
}
3. output_packet函数(ffmpeg.c)
static int output_packet(InputStream *ist, int ist_index,
OutputStream **ost_table, int nb_ostreams,
const AVPacket *pkt)
{
AVFrame picture;
AVPacket avpkt;
// 先进行解码
while (avpkt.size > 0 || (!pkt && got_output))
{
/* decode the packet if needed */
if (ist->decoding_needed) {
switch(ist->st->codec->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
ret = avcodec_decode_audio3(ist->st->codec, samples, &decoded_data_size, &avpkt);
break;
case AVMEDIA_TYPE_VIDEO:
/* XXX: allocate picture correctly */
avcodec_get_frame_defaults(&picture);
ret = avcodec_decode_video2(ist->st->codec, &picture, &got_output, &avpkt);
break;
case AVMEDIA_TYPE_SUBTITLE:
ret = avcodec_decode_subtitle2(ist->st->codec,&subtitle, &got_output, &avpkt);
break;
default:
return -1;
}
}
/* 再进行编码 */
if (start_time == 0 || ist->pts >= start_time)
for(i=0;i
if (ost->source_index == ist_index)
{
os = output_files[ost->file_index];
if (ost->encoding_needed)
{
av_assert0(ist->decoding_needed);
switch(ost->st->codec->codec_type)
{
case AVMEDIA_TYPE_AUDIO:
do_audio_out(os, ost, ist, decoded_data_buf, decoded_data_size);
break;
case AVMEDIA_TYPE_VIDEO:
do_video_out(os, ost, ist, &picture, &frame_size,same_quality ? quality : ost->st->codec->global_quality);
if (vstats_filename && frame_size)
do_video_stats(os, ost, frame_size);
break;
case AVMEDIA_TYPE_SUBTITLE:
do_subtitle_out(os, ost, ist, &subtitle,
pkt->pts);
break;
default:
abort();
}
}
}
}
av_free(buffer_to_free);
}
return 0;
}
至此,框架分析结束。