如果说我比别人看得更远些,那是因为我站在巨人的肩膀上。

                                                                                            ---- 牛顿

本文的参考和借鉴:http://dranger.com/ffmpeg/tutorial01.html(文章在开篇就声明,它有些过时了)。所以本文分析的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(); 

     av_register_all();               // This registers all file formats and codecs   

    
  /*分析命令行输入参数,进行相关的设置。*/
    init_opts();
     
     /* parse options */
     parse_options(argc, argv, options, opt_output_file);             
/*所有的数据解码,处理,编码等都是在这个函数中实现,它的函数参数可以分成三组,输出,输入以及两者间的映射关系。*/
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); 
 //av_mallocz:是ffmpeg对“malloc”的简单封装,它确保了存储地址的对齐,但不保证存储器的内存泄漏,二次释放或其它的问题。    /* 针对每个输出文件的流,找到对应输入流的索引,以得到正确的流映射 */
     for(k=0;k<nb_output_files;k++) 
     {  ... }

     /* 对每个输出流,计算相应的编码参数 */
     for(i=0;i<nb_ostreams;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<nb_ostreams;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<nb_output_files;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<nb_output_files;i++) 
     {
         os = output_files[i];
         av_write_trailer(os);
     }

    /* close each encoder */
     for(i=0;i<nb_ostreams;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<nb_ostreams;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;
 }

至此,框架分析结束。