`
chriszeng87
  • 浏览: 738738 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

FFMPEG源码分析

阅读更多

转自:http://blog.csdn.net/beitiandijun/article/details/8466432

 

以FFMPEG 1.0为参考,对FFMPEG源码分析,其中调用以H264为例

 

一、main()中;在ffmpeg.c文件中

1、OptionsContext o ={ 0 }:

初始化结构体变量o,这个结构体主要是一些参数选项;

初始化的结果是:整型和浮点型都为0,指针型成员都为NULL

疑问是,这种初始化方式到底是:

(1)初始化结构体变量的第一个成员,其他成员变量由系统采用缺省值初始化

(2)初始化所有的结构体成员

2、reset_options(&o,0):在ffmpeg_opt.c.

这是重新设置结构体体变量o,前面只是初始化,估计是以防参数选项的结构体o的某些成员变量在以往的调试过程中保留了一些参数值或者是初始化时的一些随机值,因此要将这个结构体重置,这种思想值得学习,因为我们进行反复调试的时候,可能中间强行退出,所以在退出时没有将这个参数选项的结构体释放,所以会有某些值被保留下来,会影响以后的调试或者编解码器,或者是初始化时的一些随机值恰好是参数选项的有效值,那样也会影响程序的运行结果,所以要想消除影响,只有重置这个结构体,具体如下:

 

下面列出一段reset_options(&o,0)的内容:

 

00099 void reset_options(OptionsContext *o, int is_input)
00100 {
00101     const OptionDef *po = options;
00102     OptionsContext bak= *o;
00103     int i;
00104 
00105     /* all OPT_SPEC and OPT_STRING can be freed in generic way */所有这种参数选项值都可以使用这种方式释放
00106     while (po->name) {
00107         void *dst = (uint8_t*)o + po->u.off;
00108 
00109         if (po->flags & OPT_SPEC) {
00110             SpecifierOpt **so = dst;
00111             int i, *count = (int*)(so + 1);
00112             for (i = 0; i < *count; i++) {
00113                 av_freep(&(*so)[i].specifier);
00114                 if (po->flags & OPT_STRING)
00115                     av_freep(&(*so)[i].u.str);
00116             }
00117             av_freep(so);
00118             *count = 0;
00119         } else if (po->flags & OPT_OFFSET && po->flags & OPT_STRING)
00120             av_freep(dst);
00121         po++;
00122     }
00123 
00124     for (i = 0; i < o->nb_stream_maps; i++)
00125         av_freep(&o->stream_maps[i].linklabel);
00126     av_freep(&o->stream_maps);
00127     av_freep(&o->audio_channel_maps);
00128     av_freep(&o->streamid_map);
00129 
00130     memset(o, 0, sizeof(*o));使用memset函数重置结构体O
00131 
00132     if (is_input) {
00133         o->recording_time = bak.recording_time;
00134         if (o->recording_time != INT64_MAX)
00135             av_log(NULL, AV_LOG_WARNING,
00136                    "-t is not an input option, keeping it for the next output;"
00137                    " consider fixing your command line.\n");
00138     } else
00139     o->recording_time = INT64_MAX;
00140     o->mux_max_delay  = 0.7;
00141     o->limit_filesize = UINT64_MAX;
00142     o->chapters_input_file = INT_MAX;
00143 
00144     uninit_opts();
00145     init_opts();
00146 }

 

 

 

具体分析如下:

 

 

options是一个静态恒定的OptionDef型的数组,

00142typedefstruct{

00143    constchar *name;option的名字

00144    intflags;option的标志

00145#defineHAS_ARG    0x0001即命令行含有参数选项的标志

00146#defineOPT_BOOL   0x0002布尔型数据的标志

00147#defineOPT_EXPERT 0x0004不知什么意思

00148#defineOPT_STRING 0x0008字符串的标志

00149#defineOPT_VIDEO  0x0010视频的标志

00150#defineOPT_AUDIO  0x0020音频的标志

00151#define OPT_INT    0x0080输入的标志

00152#defineOPT_FLOAT  0x0100浮点型的标志

00153#defineOPT_SUBTITLE 0x0200字幕的标志

00154#defineOPT_INT64  0x040064位int型的标志

00155#defineOPT_EXIT   0x0800退出的标志

00156#defineOPT_DATA   0x1000数据的标志

00157#defineOPT_PERFILE  0x2000    /* the option is per-file (currently ffmpeg-only).

00158                                    implied byOPT_OFFSET or OPT_SPEC */

00159#defineOPT_OFFSET 0x4000      /* option is specified as an offset in apassed optctx */

00160#defineOPT_SPEC   0x8000       /* option is to be stored in an array ofSpecifierOpt.

00161                                    ImpliesOPT_OFFSET. Next element after the offset is

00162                                    an intcontaining element count in the array. */

00163#defineOPT_TIME  0x10000时间的标志

00164#defineOPT_DOUBLE 0x20000双精度的标志

00165     union {

00166        void *dst_ptr;地址指针,后面会用到

00167         int(*func_arg)(void *,constchar *,constchar *);函数指针

00168        size_t off;偏移量的尺寸

00169    } u;共用体

00170    constchar *help;参数选项的用处

00171    constchar *argname;参数选项的名字

00172 } OptionDef;

 

options已经在ffmpeg.c中已经定义好了,可以根据上述定义对照下面的options数组:

04674staticconstOptionDefoptions[] = {

04675 #include "cmdutils_common_opts.h"

04676    { "n",OPT_BOOL,{(void *)&no_launch},"enable no-launchmode" },

04677    { "d", 0, {(void*)opt_debug},"enabledebug mode" },

04678    { "f",HAS_ARG|OPT_STRING,{(void*)&config_filename},"useconfigfile instead of /etc/ffserver.conf","configfile" },

04679    { NULL},

04680 };

 

 

00144    uninit_opts();Uninitialize the cmdutils option system, inparticular free the *_opts contexts and their contents.通过调用av_dict_free(&format_opts);av_dict_free(&codec_opts);即释放原来可能有的初始化,从进行复位

00145     init_opts();Initialize the cmdutils option system, inparticular allocate the *_opts contexts.通过调用,但是如果你想初始化,必须在configure时进行相应的配置,才可以if(CONFIG_SWSCALE)sws_opts =sws_getContext(16, 16, 0, 16, 16, 0, SWS_BICUBIC,NULL, NULL,NULL);if(CONFIG_SWRESAMPLE) swr_opts = swr_alloc();

 

 

 

3、av_log_set_flags(AV_LOG_SKIP_REPEATED):在log.c.

 

 

AV_LOG_SKIP_REPEATED这个宏定义的含义:


Skip repeated messages, this requires the user app to use av_log() insteadof (f)printf as the 2 would otherwise interfere and lead to "Last message repeatedx times" messages below (f)printf messages with some bad luck. 

跳过重复的消息;这就要求用户应用程序使用av_log()日志函数,而不是printf()函数;

 

4、parse_loglevel(argc,argv,options):cmdutils.c.

 

Find the '-loglevel' option in the command line args and apply it.

在参数命令行args中,找到’-loglevel’这个参数选项,并应用它

具体的函数内容为:

int idx = locate_option(argc,argv, options, "loglevel");

调用locate_options()函数来找loglevel这个参数选项;成功则返回loglevel所在位置的索引号,没有的话就返回0;

 

5、av_log_set_callback(log_callback_null);:log.c.

 

03126    if(argc>1 && !strcmp(argv[1],"-d")){

03127         run_as_daemon=1;守护进程标志位置1

03128         av_log_set_callback(log_callback_null);

03129         argc--;

03130         argv++;

03131    }

 

 

进入av_log_set_callback()函数,可以看到

00277voidav_log_set_callback(void (*callback)(void*,int,constchar*, va_list))

00278 {

00279    av_log_callback=callback;av_log_callback是函数指针,通过指向callback,来调用callback函数

00280 }

 

那再看一下具体的调用函数log_callback_null()函数,

03105staticvoidlog_callback_null(void *ptr,intlevel,constchar *fmt,va_list vl)

03106 {

03107 }

是个空函数,也就是说什么都不干;

 

总之,这段程序就是判断一下argv中有没有’-d’参数选项,若有,则守护进程标志位置1;守护进程标志位初始置0:

00114staticintrun_as_daemon  = 0;

 

下面是一些注册函数

6、avcodec_register_all():allcodecs.c.

 

下面是libavcodec/allcodecs.c文件开头的一句话

 

Provide registration of all codecs, parsersand bitstream filters for libavcodec.

 

 

 

函数作用:

Register all the codecs,parsers and bitstream filters which were enabled at configuration time.

If you do not call thisfunction you can select exactly which formats you want to support, by using theindividual registration functions.

即:

注册所有的编解码器、参数以及比特流滤波器,这些都是在配置阶段就启用了;

如果你不想调用这个函数,你可以准确的悬着你想要支持的格式,当然这得通过你自己的注册函数;

各位,这就是说在我们实际应用的时候,没必要非得把所有的编解码器格式都注册一遍,可以选择自己能用到的,其他的,嘿嘿,就让他们玩去吧

 

 

avcodec_register_all()函数主要调用三个函数来完成编解码器、参数以及比特流滤波器的注册。这三个函数是:

 

avcodec_register音频视频字幕编解码器的注册

av_register_codec_parser编解码器解析器的注册

av_register_bitstream_filter数据流的滤波器的注册

 

 

注册流程是:

(1)avcodec_register_all()函数调用宏定义

(2)宏定义调用具体的注册函数完成注册,就是指上面的三个函数

 

 

下面具体分析一下某些格式的注册问题,例如FFMPEG和H264的注册:

 

注意:FFMPEG本身含有H264的解码器,但是不含有编码器,只是带有编码器的接口信息,所以想生成h264格式的视频格式,必须在编译FFMPEG时将libx264编译进去

 

 

(1)注册硬件加速:

 

00059     REGISTER_HWACCEL (H264_DXVA2, h264_dxva2);
00060     REGISTER_HWACCEL (H264_VAAPI, h264_vaapi);
00061     REGISTER_HWACCEL (H264_VDA, h264_vda);

 

 

 
这是REGISTER_HWACCEL()定义:
30 #define REGISTER_HWACCEL(X,x) { \
31  extern AVHWAccel ff_##x##_hwaccel; \
32  if(CONFIG_##X##_HWACCEL) av_register_hwaccel(&ff_##x##_hwaccel); }
 
这是
 
 
 
(2)注册编解码器
00136  00136REGISTER_DECODER (H264, h264);
00137     REGISTER_DECODER (H264_CRYSTALHD, h264_crystalhd);
00138     REGISTER_DECODER (H264_VDA, h264_vda);
00139     REGISTER_DECODER (H264_VDPAU, h264_vdpau);
REGISTER_DECODER()定义为:
00037 #define REGISTER_DECODER(X,x) { \
00038           extern AVCodec ff_##x##_decoder; \
00039           if(CONFIG_##X##_DECODER)  avcodec_register(&ff_##x##_decoder); }
 
这是解码器的宏定义,先是声明AVCodec类型的编解码器为外部变量,然后调用avcodec_register()函数进行注册,看完avcodec_register()函数之
 
后,我们就知道,所谓的注册,就是将表示该编解码器的结构体加入到编解码器的列表,所以进行函数优化时,用不到的那些注册尽管删掉就是。
 
宏定义展开后:

 

解码器的注册实际为:

extern AVCodec  ff_h264_decoder;

if(CONFIG_H264_DECODER)

         avcodec_register(&ff_h264_decoder);

 

 
那现在来看一下解码器的抽象:AVCodec ff_h264_decoder:在h264.c文件中
 
 
04220 AVCodec ff_h264_decoder = {
04221     .name                  = "h264",解码器名字
04222     .type                  = AVMEDIA_TYPE_VIDEO,解码器解码数据流的类型:video
04223     .id                    = AV_CODEC_ID_H264,解码器的ID
04224     .priv_data_size        = sizeof(H264Context),解码器的私有信息
04225     .init                  = ff_h264_decode_init,解码器的初始化,使用解码器自身函数指针指向ff_h264_decode_init()函数,以供后面解码时调用
04226     .close                 = h264_decode_end,解码器的关闭,使用解码器自身结构体指针指向h264_decode_end()函数
04227     .decode                = decode_frame,解码器的解码步骤,这个是重点要看的,使用解码器自身结构体指针指向decode_frame()函数
04228     .capabilities          = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 |
04229                              CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS |
04230                              CODEC_CAP_FRAME_THREADS,
04231     .flush                 = flush_dpb,
04232     .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),解码器的长名字,更具可读性
04233     .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
04234     .update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context),
04235     .profiles              = NULL_IF_CONFIG_SMALL(profiles),
04236     .priv_class            = &h264_class,私有的类信息
04237 };
那下面就看一下解码器结构体的几个函数指针指向的函数是怎样的:
 
1)ff_h264_decode_init()函数:
函数原型为:

具体看一下:
01065     H264Context *h = avctx->priv_data;将H264的解码器的上下文信息H264Context指向编解码器的上下文信息AVCodecContext的私有信息avctx->priv_data;
01066     MpegEncContext *const s = &h->s;mpeg编码器的上下文信息
01067     int i;
01068 
01069     ff_MPV_decode_defaults(s);Set the given MpegEncContext to defaults for decoding.
the changed fields will not depend upon the prior state of the MpegEncContext.
01070 
01071     s->avctx = avctx;
01072     common_init(h);
01073 
01074     s->out_format      = FMT_H264;
上面的H264Context看一下:这个结构体太大了,只看感觉重要的:
00261 typedef struct H264Context {
00262     MpegEncContext s;MPEG编解码器的上下文信息
00263     H264DSPContext h264dsp;存放h264dsp函数的结构体
00264     int pixel_shift;    像素转换的标志
00265     int chroma_qp[2];   // QPc色度量化

00269     int prev_mb_skipped;前一个宏块skip的标志
00270     int next_mb_skipped;下一个宏块skip的标志
00271 
00272     // prediction stuff预测的事务
00273     int chroma_pred_mode;色度预测模式
00274     int intra16x16_pred_mode;帧内16x16预测模式
00275 
00276     int topleft_mb_xy;上方左边宏块
00277     int top_mb_xy;上方宏块
00278     int topright_mb_xy;上方右边宏块
00279     int left_mb_xy[LEFT_MBS];左边宏块
00280 
00281     int topleft_type;上方左边宏块的类型
00282     int top_type;上方宏块类型
00283     int topright_type;上方右边宏块类型
00284     int left_type[LEFT_MBS];左边类型
00290     int8_t(*intra4x4_pred_mode);帧内4x4预测模式
00291     H264PredContext hpc;

00323     int block_offset[2 * (16 * 3)];块的偏移
00329     int mb_linesize;    宏块的行尺寸


00335     SPS sps; SPS结构体
00336 
00340     PPS pps; // FIXME move to Picture perhaps? (->no) do we need that?PPS结构体
下面是slice的一些信息
00347     int slice_num;
00348     uint16_t *slice_table;      
00349     int slice_type;
00350     int slice_type_nos;         
00351     int slice_type_fixed;
参考帧的信息
00381     unsigned int ref_count[2];          
00382     unsigned int list_count;
00383     uint8_t *list_counts;               
00384     Picture ref_list[2][48];            
00387     int ref2frm[MAX_SLICES][2][64];     
00457     SPS *sps_buffers[MAX_SPS_COUNT];
00458     PPS *pps_buffers[MAX_PPS_COUNT];
图像信息
00488     Picture *short_ref[32];
00489     Picture *long_ref[32];
00490     Picture default_ref_list[2][32]; 
00491     Picture *delayed_pic[MAX_DELAYED_PIC_COUNT + 2]; // FIXME size?
00492     int last_pocs[MAX_DELAYED_PIC_COUNT];
00493     Picture *next_output_pic;
00494     int outputed_poc;
00495     int next_outputed_poc;
00504     int long_ref_count;     
00505     int short_ref_count;    
00507     int cabac_init_idc;cabac初始的idc
00518     int current_slice;
00534     int last_slice_type;
00548     int prev_interlaced_frame;
00581     int recovery_frame;
00586     int valid_recovery_point;
00591     // Timestamp stuff
00592     int sei_buffering_period_present;   
00593     int initial_cpb_removal_delay[32];  
00595     int cur_chroma_format_idc;
00597     int16_t slice_row[MAX_SLICES]; 
00599     int sync;  同步标志                   
00601     uint8_t parse_history[4];
00602     int parse_history_count;
00603     int parse_last_mb;
00604 } H264Context;
 
 
可视组件库的初始化
 
sei:(视频编解码) Supplemental Enhancement Information 补充的增强信息
 
 
01097     ff_h264_reset_sei(h);
 
 
 
 
 
2)数据帧的解码步骤函数:decode_frame()函数:
函数原型为:
04049 static int decode_frame(AVCodecContext *avctx, void *data,
04050                         int *data_size, AVPacket *avpkt)
04051 {
04052     const uint8_t *buf = avpkt->data;图像的数据信息,放进缓存
04053     int buf_size       = avpkt->size;图像包的尺寸,即缓存的尺寸
04054     H264Context *h     = avctx->priv_data;私有数据的指向
04055     MpegEncContext *s  = &h->s;
04056     AVFrame *pict      = data;帧结构
04057     int buf_index      = 0;
04058     Picture *out;
04059     int i, out_idx;
04060 
04061     s->flags  = avctx->flags;
04062     s->flags2 = avctx->flags2;
传递的参数:
解码器的上下文信息:AVCodecContext *avctx;
解码后的信息:void *data
解码后的数据尺寸:int* data_size
解码前的数据:AVPacket* avpkt
 
下边就是具体的解码步骤了,可惜我看不懂
04093     if(h->is_avc && buf_size >= 9 && buf[0]==1 && buf[2]==0 && (buf[4]&0xFC)==0xFC && (buf[5]&0x1F) && buf[8]==0x67){
04094         int cnt= buf[5]&0x1f;
04095         const uint8_t *p= buf+6;
04096         while(cnt--){
04097             int nalsize= AV_RB16(p) + 2;
04098             if(nalsize > buf_size - (p-buf) || p[2]!=0x67)
04099                 goto not_extra;
04100             p += nalsize;
04101         }
04102         cnt = *(p++);
04103         if(!cnt)
04104             goto not_extra;
04105         while(cnt--){
04106             int nalsize= AV_RB16(p) + 2;
04107             if(nalsize > buf_size - (p-buf) || p[2]!=0x68)
04108                 goto not_extra;
04109             p += nalsize;
04110         }
04111 
04112         return ff_h264_decode_extradata(h, buf, buf_size);
04113     }
解码网络头
04114 not_extra:
04115 
04116     buf_index = decode_nal_units(h, buf, buf_size);解码网络层单元信息
下面是应该是解码宏块了吧
04133     if (!(s->flags2 & CODEC_FLAG2_CHUNKS) ||
04134         (s->mb_y >= s->mb_height && s->mb_height)) {
04135         if (s->flags2 & CODEC_FLAG2_CHUNKS)
04136             decode_postinit(h, 1);
上面decode_postinit(h,1)函数的原型是:
static void decode_postinit(H264Context *h,
int setup_finished 
)[static]
Run setup operations that must be run after slice header decoding.运行设置的操作,但是必须是在slice头信息解码之后
This includes finding the next displayed frame.这里包含了下一个播放的帧。
Parameters:
h h264 master context
setup_finished enough NALs have been read that we can call ff_thread_finish_setup()
 
 
 
下面是等待下一个域
04140         /* Wait for second field. */
04141         *data_size = 0;
04142         if (h->next_output_pic && (h->next_output_pic->sync || h->sync>1)) {
04143             *data_size = sizeof(AVFrame);
04144             *pict      = h->next_output_pic->f;
04145         }


04152     return get_consumed_bytes(s, buf_index, buf_size);returns the number of bytes consumed for building the current frame
函数原型:


static int get_consumed_bytes(MpegEncContext *s,
int buf_size  
)
returns the number of bytes consumed for building the current frame
返回建立当前帧的字节数,注意,这个字节数是假设的,并不是实际的

 
 
 
3)解码器关闭阶段:h264_decode_end()函数
 
函数原型:
04168 static av_cold int h264_decode_end(AVCodecContext *avctx)
04169 {
04170     H264Context *h    = avctx->priv_data;私有信息指针
04171     MpegEncContext *s = &h->s;
04172 
04173     ff_h264_remove_all_refs(h);
04174     ff_h264_free_context(h);释放H264Context中的所有数据:例如sps和pps,还有各种量化的表格等
04175 
04176     ff_MPV_common_end(s);
04177 
04178     // memset(h, 0, sizeof(H264Context));
04179 
04180     return 0;
04181 }
 
 
 
 
上面是H264解码器的结构体信息,现在来看这个解码器的注册:
 
 
 
void avcodec_register(AVCodec* codec):util.c文件中
 
Register the codec codec and initialize libavcodec.

Warning:
either this function or avcodec_register_all() must be called before any other libavcodec functions.
 
 
 
 
此函数功能是注册编解码器codec,并且初始化libavcodec
 
将编解码器保存在静态全局变量first_avcodec中,这是一个链表结构体,当用到时,遍历这个链表即可
 
00150 
00151voidavcodec_register(AVCodec *codec)
00152 {
00153     AVCodec **p;将p定义成二级指针,估计是为了下面将编解码器连成编解码器链表的方便;p指向整个链表的首地址,使用*p存放每一个编解码器的首地址,**p就是表示编解码器的具体的字符串形式;00154     avcodec_init();编解码器的初始化,且只能初始化一次。其中负责静态查找表结构的初始化的函数:ff_dsputil_static_init(),主要是两个表结构的初始化,ff_cropTbl和ff_squareTbl的初始化;其中ff_cropTble[i]实现的功能实际上是a = i<0 ? 0 : (i>255 ? 255 : i);这个表的大小是2*MAX_NEG_CROP+256, 是将-1024到1024之间的任意数转化为0~255的数,1024就是MAX_NEG_CROP的值。这样设计的实质是用查表的方式来减少所需执行的指令数量,是用空间上的代价来换取速度的提升,这是一种典型的方式。00155     p = &first_avcodec;前面有first_avcodec的静态全局变量初始化,即static AVCodec* first_avcodec = NULL;除了第一次添加avcodec时p会指向NULL,其他时候都是指向链表的首地址,实际上first_avcode存放的就是avcodec链表的首地址;不过感觉每一次调用avcodec_register()函数都要从开始遍历编解码器链表,这样不是很浪费时间么00156     while (*p != NULL)
00157         p = &(*p)->next;
00158     *p          = codec;
00159     codec->next = NULL;上边几行代码一块来看,就是将编解码器连接成链表的过程;先遍历已经存在的编解码器链表,然后将当前的编解码器插入到链表的结尾00160 
00161     if (codec->init_static_data)这个函数指针主要完成编解码器静态数据的初始化00162         codec->init_static_data(codec);想进入看看这个函数,但是没进去。00163 }
 
来具体看一下avcodec_init()函数:
 

来看一下avcodec_init()函数是怎样进行初始化的。

00130staticvoidavcodec_init(void)

00131 {

00132    static int initialized = 0;

00133

00134    if (initialized != 0)

00135        return;

00136    initialized = 1;

00137

00138    ff_dsputil_static_init();

00139 }

调用了ff_dsputil_static_init()函数,那就继续跟进看一下:

这个函数主要是初始化avcodec,需要保证在编解码之前完成ff_dsputil_static_init()函数,但是该函数只能初始化一次,保证其初始化一次的方式就是:使用initialized变量,当initialied=1时,就返回,所以只有第一次注册编解码器才执行ff_dsputil_static_init()函数,这样就保证了ff_dsputil_static_init()函数只执行一次。

 

ff_dsputil_static_init()函数主要是对一些静态查找表结构的初始化:

02782av_coldvoidff_dsputil_static_init(void)

02783 {

02784    int i;

02785

02786    for(i=0;i<256;i++)ff_cropTbl[i+MAX_NEG_CROP]= i;

02787    for(i=0;i<MAX_NEG_CROP;i++){

02788         ff_cropTbl[i]= 0;

02789         ff_cropTbl[i+ MAX_NEG_CROP + 256] = 255;

02790     }其中ff_cropTble[i]实现的功能实际上是a = i<0 ? 0 : (i>255 ? 255 : i);这个表的大小是2*MAX_NEG_CROP+256, 是将-10241024之间的任意数转化为0~255的数,1024就是MAX_NEG_CROP的值。这样设计的实质是用查表的方式来减少所需执行的指令数量,是用空间上的代价来换取速度的提升,这是一种典型的方式。

 

02791

02792    for(i=0;i<512;i++) {

02793         ff_squareTbl[i]= (i - 256) * (i - 256);

02794    }

02795

02796    for(i=0; i<64; i++)ff_inv_zigzag_direct16[ff_zigzag_direct[i]]=i+1;

02797 }

MAX_NEG_CROP的值,用作pixel opration:

00087 /* pixel operations */

00088#defineMAX_NEG_CROP 1024

 

但是最后一点有点不明白:

00161    if (codec->init_static_data)

00162        codec->init_static_data(codec);

此时codec还没初始化,这里做一个判断codec->init_static_data这个函数指针是否为NULL,若是不为空指针,则初始化该codec的静态数据;但是这里就是做codec的初始化,估计这个codec->init_static_data指针还没指向某个函数,应该是空;

 

 
(3)注册音频编解码器
00310     REGISTER_DECODER (MP3, mp3);
这个先不看,以后有时间再看音频解码器的注册。
 
 
 
(4)外部的库文件,以H264和mp3为例
00450     REGISTER_ENCODER (LIBX264, libx264);
00435     REGISTER_ENCODER (LIBMP3LAME, libmp3lame);
这里主要看libx264库的注册:
 
这个库文件的注册和上边vcodec的注册是一样的,都是调用avcodec_register()函数完成,同样也是讲库文件添加到AVCodec链表中去
 
宏定义展开后,以libx264为例:

编码器的注册实际为:

extern AVCodec  ff_libx264_encoder;

if(CONFIG_libx264_ENCODER)

         avcodec_register(&ff_libx264_encoder);

 

在这里我们就看一下ff_libx264_encoder的具体内容是什么:libavcodec/libx264.c

 

由于ffmpeg源代码中只有H264格式视频的解码器,所以要想编码生成H264格式的视频,需要在configure时编译libx264库文件

00701 AVCodec ff_libx264_encoder = {
00702     .name             = "libx264",编码器的名字是libx26400703     .type             = AVMEDIA_TYPE_VIDEO,处理的多媒体类型是video
00704     .id               = AV_CODEC_ID_H264,编码器的ID
00705     .priv_data_size   = sizeof(X264Context),私有数据的尺寸,这是x264 codec特有的上下文信息;刚开始看的时候不明白,原来这个编解码器的priv_data就是指各种编解码器自身特有的一些特点
00706     .init             = X264_init,编码器的初始化,使用编码器结构体自身函数指针指向X264_init()函数
00707     .encode2          = X264_frame,编码器的具体实现,使用编码器结构体自身函数指针指向X264_frame()函数
00708     .close            = X264_close,编码器的关闭,使用编码器结构体自身函数指针指向X264_close()函数
00709     .capabilities     = CODEC_CAP_DELAY | CODEC_CAP_AUTO_THREADS,
00710     .long_name        = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),编码器的长名字,更具可读性
00711     .priv_class       = &class,私有的类
00712     .defaults         = x264_defaults,缺省值,
00713     .init_static_data = X264_init_static,初始化静态数据,使用编码器结构体自身函数指针指向X264_init_static()函数
00714 };

 

 

上面通过对编解码器的注册,调用AVCodec ff_libx264_encoder的部分初始化,借助结构体AVCodec自身的一些函数指针,指向一些调用编码器的函数,以供后面具体转码时调用

 

那就看一下具体的函数调用:

 

1)X264_init()函数:

函数原型为:

 

00279 static av_cold int X264_init(AVCodecContext *avctx)

 

 

也就是说ffmpeg是通过AVCodecContext结构体来向x264传递参数的

 

 

00281     X264Context *x4 = avctx->priv_data;通过avctx->priv_data将编码器参数传递给x264的编码器上下文
00282     int sw,sh;
00283 
00284     x264_param_default(&x4->params);这个就是直接调用x264中的函数了,进行缺省参数的设置

 

下边继续看结构体X264Context:

摘录一些感觉比较重要的:

 

00035 typedef struct X264Context {
00036     AVClass        *class;x264编码器的类信息
00037     x264_param_t    params;x264编码器参数的结构体
00038     x264_t         *enc;x264编码器的句柄,就好比ffmpeg中AVFormatContext一样
00039     x264_picture_t  pic;x264编码器的图像的结构体
00042     AVFrame         out_pic;输出的帧结构
00050     float crf;质量模式
00051     float crf_max;
00052     int cqp;
00062     int b_bias;B帧的插入概率,0~100,越大插入B帧的概率越大
00063     int b_pyramid;允许B帧做参考帧
00066     int fast_pskip;p宏块skip模式
00072     int direct_pred;直接预测模式
00073     int slice_max_size;slice的最大尺寸
00076 } X264Context;

 

缺省参数初始化完毕后,就是编码器的打开:

 

00533     x4->enc = x264_encoder_open(&x4->params);直接调用x264中x264_t *x264_encoder_open( x264_param_t * )函数,将编码器打开
00534     if (!x4->enc)
00535         return -1;
00536 

 

然后就是编码帧的等价:

 

00537     avctx->coded_frame = &x4->out_pic;将二者对应起来

 

然后就是

00539     if (avctx->flags & CODEC_FLAG_GLOBAL_HEADER) {
00540         x264_nal_t *nal;
00541         uint8_t *p;
00542         int nnal, s, i;
00543 
00544         s = x264_encoder_headers(x4->enc, &nal, &nnal);
这个主要是调用x264_encoder_headers()函数,作用是返回x264整个编码过程中都会用到的SPS and PPS ;
 
 
 
2)现在来看X264_frame()函数
 
函数原型是:
00151 static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame,
00152                       int *got_packet)
传入的参数包括:
FFMPEG编码器的上下文信息AVCodecContext:这是编解码时必须的 信息
数据包AVPacket:原始数据
数据帧AVFrame:编码后的数据
 
 
00154     X264Context *x4 = ctx->priv_data;还是先通过AVCodecContext向x264传递一些私有信息
00155     x264_nal_t *nal;网络层信息
00156     int nnal, i, ret;
00157     x264_picture_t pic_out;输出图像的结构体
 
接着是:
00159     x264_picture_init( &x4->pic );
直接调用 x264中的 x264_picture_init()函数,进行图像的初始化
 
接下来是重点:
这才是FFMPEG调用X264编码器的关键所在:
00189     do {
00190         if (x264_encoder_encode(x4->enc, &nal, &nnal, frame? &x4->pic: NULL, &pic_out) < 0)直接调用libx264库中的x264_encoder_encode()函数进行编码,前面所有的东西都是为了调用这个函数做准备的。00191             return -1;
00192 
00193         ret = encode_nals(ctx, pkt, nal, nnal);对网络层信息进行编码
00194         if (ret < 0)
00195             return -1;
00196     } while (!ret && !frame && x264_encoder_delayed_frames(x4->enc));
 
下边就是一些时间戳信息和帧类型信息的确认
00198     pkt->pts = pic_out.i_pts;
00199     pkt->dts = pic_out.i_dts;
00200 
00201     switch (pic_out.i_type) {
00202     case X264_TYPE_IDR:
00203     case X264_TYPE_I:
00204         x4->out_pic.pict_type = AV_PICTURE_TYPE_I;
00205         break;
00206     case X264_TYPE_P:
00207         x4->out_pic.pict_type = AV_PICTURE_TYPE_P;
00208         break;
00209     case X264_TYPE_B:
00210     case X264_TYPE_BREF:
00211         x4->out_pic.pict_type = AV_PICTURE_TYPE_B;
00212         break;
00213     }
 
 
3)现在来看X264_close()函数:
00223 static av_cold int X264_close(AVCodecContext *avctx)
00224 {
00225     X264Context *x4 = avctx->priv_data;
00226 
00227     av_freep(&avctx->extradata);
00228     av_free(x4->sei);
00229 
00230     if (x4->enc)
00231         x264_encoder_close(x4->enc);直接调用x264_encoder_close()函数,释放编码器占有的资源
00232 
00233     return 0;
00234 }
 
(5)注册解析器
00478     REGISTER_PARSER  (H264, h264);
这是REGISTER_PARSER(X,x)的定义:
 
00042 #define REGISTER_PARSER(X,x) { \
00043           extern AVCodecParser ff_##x##_parser; \
00044           if(CONFIG_##X##_PARSER)  av_register_codec_parser(&ff_##x##_parser); }

 

 

宏定义的功能是:
 
先将当前编解码器的解析器声明为外部函数,以方便调用;
 
调用av_register_codec_parser()函数进行注册此种编解码器的解析器;
 

解析器的注册实际为:

extern AVCodecParser  ff_h264_parser;

if(CONFIG_H264_PARSER)

         avcodec_register(&ff_h264_parser);

 
那好让我们来实际看一下H264解析器是什么样的:libavcodec/h264_parser.c
 
00392 AVCodecParser ff_h264_parser = {
00393     .codec_ids      = { AV_CODEC_ID_H264 },当前解码器所服务的编解码器的id,当然是AV_CODEC_ID_H264
00394     .priv_data_size = sizeof(H264Context),私有数据的尺寸,因为私有数据的指针指向H264Context
00395     .parser_init    = init,解析器的初始化,使用结构体自身的函数指针指向init()函数
00396     .parser_parse   = h264_parse,解析器的解析步骤,使用结构体自身的函数指针指向h264_parse()函数
00397     .parser_close   = close,解析器的关闭步骤,使用结构体自身的函数指针指向close()函数
00398     .split          = h264_split,解析器的划分?这个是干什么的
00399 };
 
1)先来看解析器的初始化步骤:init()
函数原型为:
00384 static int init(AVCodecParserContext *s)
00385 {
00386     H264Context *h = s->priv_data;H264Context指向私有数据
00387     h->thread_context[0] = h;
00388     h->s.slice_context_count = 1;
00389     return 0;
00390 }
2)接下来是解析器具体解析的步骤:h264_parse()函数:
函数原型为:
00290 static int h264_parse(AVCodecParserContext *s,当前解析器的上下文信息
00291                       AVCodecContext *avctx,编解码器上下文信息
00292                       const uint8_t **poutbuf, int *poutbuf_size,
00293                       const uint8_t *buf, int buf_size)缓冲区以及缓冲区的尺寸
00294 {
00295     H264Context *h = s->priv_data;指向当前编解码器的私有数据
00296     ParseContext *pc = &h->s.parse_context;
00297     int next;
00299     if (!h->got_first) {h->got_first是解析是否成功的标志,如果此值!=0,表示成功解析一帧
00300         h->got_first = 1;
00301         if (avctx->extradata_size) {额外的数据,有些多媒体文件需要这个
00302             h->s.avctx = avctx;
00303             // must be done like in decoder, otherwise opening the parser,
00304             // letting it create extradata and then closing and opening again
00305             // will cause has_b_frames to be always set.
00306             // Note that estimate_timings_from_pts does exactly this.
00307             if (!avctx->has_b_frames)在解码器中帧重新排序缓存的尺寸,若是等于零,则表明没有B帧存在
00308                 h->s.low_delay = 1;重新排序的标志置1,标志没有重新排序的需要,或者说没有B帧
00309             ff_h264_decode_extradata(h, avctx->extradata, avctx->extradata_size);解码额外的数据
00310         }
00311     }
00313     if(s->flags & PARSER_FLAG_COMPLETE_FRAMES){如果已经完成解析,则
00314         next= buf_size;
00315     }else{否则:
00316         next= ff_h264_find_frame_end(h, buf, buf_size);根据缓存和缓存尺寸寻找帧结尾
00317 
00318         if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {将截断的比特流连成一个完整的帧
00319             *poutbuf = NULL;
00320             *poutbuf_size = 0;
00321             return buf_size;
00322         }
00330     parse_nal_units(s, avctx, buf, buf_size);解析网络层
00348     return next;返回buf_size:
 
 
3)关闭解析器步骤:close()
函数原型:
00375 static void close(AVCodecParserContext *s)
00376 {
00377     H264Context *h = s->priv_data;
00378     ParseContext *pc = &h->s.parse_context;
00379 
00380     av_free(pc->buffer);释放缓存
00381     ff_h264_free_context(h);释放H264Context结构体
00382 }
00383 
 
av_register_codec_parser():parser.c文件中
 
 
00035 void av_register_codec_parser(AVCodecParser *parser)
00036 {
00037     parser->next = av_first_parser;av_first_parser也是在前面已经定义好的静态全局变量:static AVCodecParser *av_first_parser = NULL;
00038     av_first_parser = parser;
00039 }本函数完成功能也是讲解析器连接成解析器链表;先让parser->next指向NULL指针av_first_parser,然后再将av_first_parser指向当前的解析器,也就是把当前的解析器连接到链表的尾部。
 
(6)还有其他的一些注册,如数据流滤波器的注册等,方式和上边两种注册方式基本一致。
 
void  av_register_bitstream_filter(AVBitStreamFilter* bsf)
 
下面是具体代码:
00033 void av_register_bitstream_filter(AVBitStreamFilter *bsf){
00034     bsf->next = first_bitstream_filter;
00035     first_bitstream_filter= bsf;
00036 }此函数的注册方式和上面codec_parser的注册方式一致
 
 

 

7、avdevice_regiser_all():alldevices.c.

 

 

官网上的解释:

 

Initializelibavdevice and register all the input and output devices.

Warning:

This function is not thread safe.

也就是说初始化库libavdevice,并且注册所有的输入和输出设备。

Register all the grabbing devices

这个是libavdevice/alldevices.c文件开头的一句话,明白了,原来是注册所有的多媒体捕捉设备,包括输入和输出设备,这些设备可以是硬件实现,也可以是软件实现。

 

avdevice_register_all()函数的具体内容是:

 

 

00032voidavdevice_register_all(void)

00033 {

00034    staticint initialized;

00035

00036    if (initialized)

00037        return;

00038    initialized = 1;通过initialized变量的设置,来说明该函数avdevice_register_all()只能被有效调用一次,即所有的device只能注册一次。

00039

00040    /* devices */

00041    REGISTER_INOUTDEV(ALSA, alsa);Advanced Linux Sound Architecture,一种linux下的音频架构,它在linux上提供音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持,在2.6系列的内核中ALSA已经成为默认的声音子系统,来替换2.4内核系列的OSS(OPEN SOUND SYSTEM,开放声音系统)

00042    REGISTER_INDEV    (BKTR, bktr);

00043    REGISTER_OUTDEV   (CACA, caca);

00044    REGISTER_INDEV    (DSHOW, dshow);即DirectShow,微软开发基于COM的流媒体处理的开发包,广泛的支持各种多媒体格式,包括asf、mpeg、avi、dv、mp3、wave等,为多媒体流的捕捉和回放提供强有力的支持。

00045    REGISTER_INDEV    (DV1394, dv1394);高速处理的视频采集卡,分为软件和硬件实现两种。

00046    REGISTER_INDEV    (FBDEV, fbdev);

00047    REGISTER_INDEV    (IEC61883, iec61883);

00048    REGISTER_INDEV    (JACK, jack);

00049    REGISTER_INDEV    (LAVFI, lavfi);

00050    REGISTER_INDEV    (OPENAL, openal);

00051    REGISTER_INOUTDEV(OSS, oss);

00052    REGISTER_INDEV    (PULSE, pulse);

00053    REGISTER_OUTDEV   (SDL, sdl);开放源码的跨平台多媒体开发库,提供了数种控制图像、声音、输入输出的函数,让开发者只要使用相同或者相似的代码就可以开放出跨平台的应用软件。Simple DirectMedia Layer

00054    REGISTER_INOUTDEV(SNDIO, sndio);

00055    REGISTER_INDEV    (V4L2, v4l2);Video 4 for linux 2;针对于uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。只能用于linux;包含了

1、视频采集接口:可以是高频头或者摄像头

2、视频输出接口:可以驱动计算机的外围视频图像设备—像可以输出电视信号格式的设备

3、直接传输视频接口:它的工作主要是把视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU。

4、视频间隔消隐信号接口:他能使应用可以访问传输消隐期的视频信号

5、收音机接口:可用来处理从AM或FM高频头设备接受来的音频流。

00056 //   REGISTER_INDEV    (V4L, v4l

00057    REGISTER_INDEV    (VFWCAP, vfwcap);

00058    REGISTER_INDEV    (X11GRAB,x11grab);windows下的一种屏幕录像工具

00059

00060    /* externallibraries */

00061    REGISTER_INDEV    (LIBCDIO, libcdio);

00062    REGISTER_INDEV    (LIBDC1394, libdc1394);

00063 }

 

 

avdevice_register_all()函数主要是通过调用两个函数具体实现注册输入输出设备:

 

av_register_input_format():utils.c文件中

av_register_output_format():utils.c文件中

 

注册流程:

(1)avdevice_register_all()调用宏定义REGISTER_*

(2)REGISTER_*调用具体的注册函数:av_register_input_format()、av_register_output_format()。

 

 

(1)输出设备的注册:

 

下面是avdevice_register_all()函数调用的SDL的宏定义:

 

 

00053     REGISTER_OUTDEV   (SDL, sdl);

 

 

在编译阶段,会提示如果不安装SDL,不会生成ffplay,不知道这个SDL是不是生成ffplay的SDL。

 

这个是REGISTER_OUTDEV的宏定义:

 

 

00024 #define REGISTER_OUTDEV(X,x) { \
00025           extern AVOutputFormat ff_##x##_muxer; \
00026           if(CONFIG_##X##_OUTDEV)  av_register_output_format(&ff_##x##_muxer); }

 

 

上面的宏定义先声明AVOutputFormat类型的muxer(复用器或者说混流器)

然后调用av_register_output_foramt()函数进行注册

 

 

拿SDL来说吧:

00053     REGISTER_OUTDEV   (SDL, sdl);

可以转化成:

externAVOutputFormat ff_sdl_muxer;

if(CONFIG_SDL_OUTDEV)

    av_register_output_format(&ff_sdl_muxer);

 

 

av_register_output_format():utils.c文件中

 

将muxer保存在静态全局变量first_oformat中,这也是一个链表,当用到muxer时,通过遍历的方式在first_oformat中查找所需要muxer即可;这一点和codec的注册方式相同

 

 

00157 void av_register_output_format(AVOutputFormat *format)
00158 {
00159     AVOutputFormat **p;和上面avcodec_register()的用意一样,都是定义成二级指针,方便处理;p指向复用器muxer链表的首地址,*p指向每一个muxer的首地址,**p是指表示muxer名字
00160     p = &first_oformat;这是本程序前面的声明:static AVOutputFormat *first_oformat = NULL;注册之前先让二级指针p指向NULL型指针,当然除了第一次注册是指向NULL指针之外,后面的注册都不再指向NULL指针了,因为first_oformat不再为空,它其实指向muxer的链表的首地址。
00161     while (*p != NULL) p = &(*p)->next;
00162     *p = format;
00163     format->next = NULL;和前面一样,先遍历一遍muxer的链表,然后将当前的muxer加到链表的尾部;
00164 }

 

 

(2)输入设备的注册和输出设备的注册一致:

 

 

 

00055     REGISTER_INDEV    (V4L2, v4l2);

 

 

宏定义具体为:

 

00027 #define REGISTER_INDEV(X,x) { \
00028           extern AVInputFormat ff_##x##_demuxer; \
00029           if(CONFIG_##X##_INDEV)   av_register_input_format(&ff_##x##_demuxer); }

 

 

也是先做一个外部变量的声明,即声明某种demuxer为全局变量,方便后面程序调用这个demuxer;

 

然后调用av_register_input_format()函数进行具体的注册;

 

av_register_input_format():utils.c文件中

 

将demuxer保存在静态全局变量first_iformat中,这也是一个链表,当用到demuxer时,通过遍历的方式在first_iformat中查找所需要demuxer即可;这一点和muxer的注册方式相同

 

 

00148voidav_register_input_format(AVInputFormat *format)
00149 {
00150     AVInputFormat **p;和上面av_register_input_format()的用意一样,都是定义成二级指针,方便处理;p指向复用器demuxer链表的首地址,*p指向每一个demuxer的首地址,**p是指表示demuxer名字00151     p = &first_iformat;这是本程序前面的声明:static AVInputFormat *first_iformat = NULL;注册之前先让二级指针p指向NULL型指针,当然除了第一次注册是指向NULL指针之外,后面的注册都不再指向NULL指针了,因为first_iformat不再为空,它其实指向demuxer的链表的首地址00152     while (*p != NULL) p = &(*p)->next;
00153     *p = format;
00154     format->next = NULL;和前面一样,先遍历一遍demuxer的链表,然后将当前的muxer加到链表的尾部;00155 }

 

 

(3)还有一种是同时具备输入和输出功能的设备,这种设备的注册就是将上面两种注册同时进行即可。

 

00051     REGISTER_INOUTDEV (OSS, oss);

 

 

宏定义为:

 

 

00030 #define REGISTER_INOUTDEV(X,x)  REGISTER_OUTDEV(X,x); REGISTER_INDEV(X,x)

 

 

同时注册输入和输出设备;具体过程不再分析,和上面输入输出的注册相同。

 

 

8、avfilter_register_all():allfilters.c.

初始化滤波器系统,注册所有的内置滤波器;

 

滤波器的注册和上边的注册大同小异,因不是关注的重点,暂时就不写源码分析了。

(1)先调用宏定义,

(2)宏定义中调用函数avfilter_register_all()函数具体实现滤波器的注册;

 

但是要注意的是:一种滤波器是FFMPEG调用外部的滤波器;另一种滤波器是FFMPEG内置的滤波器。

具体注册函数为

avfilter_register():在avfilters.c文件中

函数功能是:注册滤波器;仅当你在后面打算通过avfilter_get_by_name()函数使用名字去寻找AVFilter结构时,这个函数才会用到;即使你没有注册滤波器,那后面的avfilter_open()函数依然可以为你实现滤波器。

 

9、av_register_all():allformats.c.

官网上给出的解释是:

 

Initializelibavformat and register all the muxers, demuxers and protocols.

If you do not callthis function, then you can select exactly which formats you want to support.

 

初始化libavformat,并且注册所有的复用器、解复用器和网络协议

如果你不想调用这个函数,你可以选择使用你想支持的格式;

这个和上面编解码器的注册一样,都可以自己编写,没必要啰啰嗦嗦一大堆东西

 

注册完编解码器、输入输出设备以及滤波器,终于轮到demuxer和muxer的注册了,其实。

 

 

av_register_all()函数主要通过调用下面三个函数实现具体的注册活动:

(1)av_register_input_format():utils.c文件中

(2)av_register_output_format():utils.c文件中

(3)ffurl_register_protocol():avio.c文件中;原来的av_register_protocol()函数已经不再用了

 

注册过程:

(1)先调用宏定义,例如H264调用为:REGISTER_MUXDEMUX(H264,h264)

(2)然后通过宏定义调用muxer和demuxer的注册函数

 

 

00002  *Register all the formats and protocols

00041voidav_register_all(void)

00042 {

00043    static int initialized;

00044

00045    if (initialized)

00046        return;

00047    initialized = 1;

00048

00049    avcodec_register_all();

 

这个函数也是使用initialized变量来控制本函数只能被注册一次。

在这函数中,调用了前面的avcodec_register_all()函数,但是由于前面注册过了,所以这里不会重新注册,估计是预备前面没有注册的。

 

具体实现过程为:

(1)先调用宏定义:

 

00114     REGISTER_MUXDEMUX (H264, h264);

 

 

(2)宏定义的内容为:因为H264同时为muxer和demuxer,所以将三个宏定义都列上:

 

00027 #define REGISTER_MUXER(X,x) { \
00028     extern AVOutputFormat ff_##x##_muxer; \
00029     if(CONFIG_##X##_MUXER) av_register_output_format(&ff_##x##_muxer); }
00030 
00031 #define REGISTER_DEMUXER(X,x) { \
00032     extern AVInputFormat ff_##x##_demuxer; \
00033     if(CONFIG_##X##_DEMUXER) av_register_input_format(&ff_##x##_demuxer); }
00034 
00035 #define REGISTER_MUXDEMUX(X,x)  REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)

 

 

宏定义先声明muxer和demuxer的外部变量,在ffmpeg中muxer和demuxer分别抽象为:AVOutputFormat和AVInputFormat,muxer为混流器,demuxer为分流器

 

然后调用av_register_output_format()和av_register_input_format()分别完成muxer和demuxer的注册

 

muxer和demuxer的注册过程在前面avdevice_register_all()中已经讲过,这里就不再重复了。

 

宏定义替换后为:

extern AVOutputFormat ff_h264_muxer;

if(CONFIG_H264_MUXER)

         av_register_output_format(&ff_h264_muxer);

 

extern AVInputFormat ff_h264_demuxer;

if(CONFIG_H264_DEMUXER)

         av_register_input_foramt(&ff_h264_demuxer);

这就把H264的复用器和解复用器都注册上了

 

 

协议的注册:

 

以RTP的实现为例:

 

00297     REGISTER_PROTOCOL (RTP, rtp);

下面是宏定义的具体内容:

 

 

00037 #define REGISTER_PROTOCOL(X,x) { \
00038     extern URLProtocol ff_##x##_protocol; \
00039     if(CONFIG_##X##_PROTOCOL) ffurl_register_protocol(&ff_##x##_protocol, sizeof(ff_##x##_protocol)); }

 

 

先将URLProtocol型的协议声明为外部变量,URLProtocol是对协议的抽象

然后调用ffurl_register_protocol()实现具体的注册活动;

 



int ffurl_register_protocol(URLProtocol * protocol,int  size):avio.c文件中

功能是注册URLProtocol类型的协议,其中size是URLProtocol结构体类型的尺寸,可以过滤掉协议长度太长的协议;

 

00096 int ffurl_register_protocol(URLProtocol *protocol, int size)协议抽象为URLProtocol类型的结构体
00097 {
00098     URLProtocol **p;定义二级指针是为了方便后面协议查找,其中p指向协议链表的首地址,*p指向每一个协议的首地址,**p指向具体协议内容
00099     if (size < sizeof(URLProtocol)) {先判断一下size尺寸是否满足protocol的需要,不能的话就从新分配空间,将协议复制到新分配的空间中00100         URLProtocol* temp = av_mallocz(sizeof(URLProtocol));
00101         memcpy(temp, protocol, size);
00102         protocol = temp;
00103     }上面这几句可以看成是过滤掉长度大于FFMPEG所支持的协议长度的一些协议
00104     p = &first_protocol;first_protocol是在这个函数之前声明的静态全局变量: static URLProtocol *first_protocol = NULL;除了第一次注册时p为NULL指针外,其余时候均不为NULL指针,因为一旦注册开始,first_protocol其实就是整个协议注册链表的首地址。
00105     while (*p != NULL) p = &(*p)->next;
00106     *p = protocol;
00107     protocol->next = NULL;上边这几句和前面编解码器、muxer、demuxer的注册方式一致,都是先遍历整个注册链表,然后将当前要注册的协议添加到协议链表的末尾。
00108     return 0;
00109 }

 

 

 

 

 

 

 

 

 

 

 

10、avformat_network_init():utils.c.

 

官网上给出的解释是:

 

 

Do global initialization ofnetwork components.

This is optional, butrecommended, since it avoids the overhead of implicitly doing the setup foreach session.

Calling this function willbecome mandatory if using network protocols at some major version bump.

即:

对网络组件做一个全局的初始化

这是可选择的,但是建议你最好做,因为它避免了为每个会话隐式设置的开销

调用这个函数将成为强制性的,如果你使用一些更新中的主要的版本中的网络协议。

主要调用两个函数实现功能:

ff_network_init():network.c

ff_tls_init():network.c

 

04600 int avformat_network_init(void)
04601 {
04602 #if CONFIG_NETWORK
04603     int ret;
04604     ff_network_inited_globally = 1;
04605     if ((ret = ff_network_init()) < 0)
04606         return ret;
04607     ff_tls_init();
04608 #endif
04609     return 0;
04610 }

 

 

进入ff_network_init()函数可知:

00126intff_network_init(void)

00127 {

00128 #if HAVE_WINSOCK2_H

00129    WSADATA wsaData;

00130 #endif

00131

00132    if (!ff_network_inited_globally)

00133        av_log(NULL,AV_LOG_WARNING,"Using network protocols withoutglobal "

00134                                      "network initialization. Please use"

00135                                      "avformat_network_init(), this will"

00136                                      "become mandatory later.\n");

00137 #if HAVE_WINSOCK2_H

00138    if (WSAStartup(MAKEWORD(1,1), &wsaData))

00139        return 0;

00140 #endif

00141    return 1;

00142 }

 

从上面可知这个在windows下需要进行一些网络的初始化设置的,

 

 

11、 show_banner(argc, argv, options):cmdutils.c.

 

官网上给出的解释是:

 

 

Print the program banner to stderr.

The banner contents depend on the current version of the repository and ofthe libav* libraries used by the program.

 

打印一些程序的条幅信息到标注输出;

这些条幅信息的内容依赖于当前资料库的版本以及程序所使用libav*库

 

下面是这个函数的具体内容:

 

00672 void show_banner(int argc, char **argv, const OptionDef *options)
00673 {
00674     int idx = locate_option(argc, argv, options, "version");返回argv中参数选项-version的索引号;如果参数选项没有找到对应选项的话,也就是说错误选项,那么返回0;结合下面if语句可知,找到version返回是因为参数选项设置会打印version信息,所以这里就不需要打印了;若没找到version,那么这里就要打印协议信息。
00675     if (idx)
00676         return;没有错误选项的话,就正确返回,有错误选项,就执行下面,即根据日志文件打印一些程序的版本信息;
00677 
00678     print_program_info (INDENT|SHOW_COPYRIGHT, AV_LOG_INFO);
00679     print_all_libs_info(INDENT|SHOW_CONFIG,  AV_LOG_INFO);
00680     print_all_libs_info(INDENT|SHOW_VERSION, AV_LOG_INFO);
00681 }

locate_option()函数对提供的参数选项在argv中的位置进行定位,并返回此选项在argv中索引号,如果没有找到该选项,则返回0。

 

 

 

 

 

 

 

 

 

 

 

 

12、term_init():ffmpeg.c.

这是FFMPEG对键盘操作响应的 初始化;

 

 

 

 

00283 void term_init(void)
00284 {
00285 #if HAVE_TERMIOS_H
00286     if(!run_as_daemon){
00287         struct termios tty;定义一个termios结构体类型的数据tty
00288         int istty = 1;
00289 #if HAVE_ISATTY
00290         istty = isatty(0) && isatty(2);查看是否为标准输入和标准错误
00291 #endif
00292         if (istty && tcgetattr (0, &tty) == 0) {获取标准输入的状态并进行标准输入和标准错误判断
00293             oldtty = tty;
00294             restore_tty = 1;
00295             atexit(term_exit);
00296 以下是进行raw模式设置
00297             tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
00298                              |INLCR|IGNCR|ICRNL|IXON);
00299             tty.c_oflag |= OPOST;
00300             tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
00301             tty.c_cflag &= ~(CSIZE|PARENB);
00302             tty.c_cflag |= CS8;
00303             tty.c_cc[VMIN] = 1;
00304             tty.c_cc[VTIME] = 0;
00305 
00306             tcsetattr (0, TCSANOW, &tty);
00307         }
00308         signal(SIGQUIT, sigterm_handler); /* Quit (POSIX).  */POSIX标准模式下的退出信号处理,当收到SIGQUIT时,进行退出
00309     }
00310 #endif
00311     avformat_network_deinit();
00312 
00313     signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */键盘产生中断
00314     signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */进程终止信号
00315 #ifdef SIGXCPU
00316     signal(SIGXCPU, sigterm_handler);CPU时间限制被打破
00317 #endif
00318 }

 

 

 

本函数其实是借用了termios系列函数的部分功能(在上面用到了tcgetattr和tcgetattr函数)、isatty()函数、signal()函数,以实现程序与用户之间的交互。

 

termios系列函数:

对终端进行读写操作:

当一个程序在命令提示符中被调用时,shell负责将标准输入和标准输出流连接到你的程序,

实现程序与用户之间的交互。

 

termios函数族提供了一个常规的终端接口,用于控制非同步通信端口;

termios系列函数包括:

tcgetattr,tcsetattr,tcsendbreak,tcdrain,tcfush,tcflow,cfmakeraw,cfgetospeed,cfgetispeed,cfsetispeed,cfsetospeed,cfsetspeed等,用于获取或者设置终端设备的属性、速度、控制等

 

 

上面那些函数都是根据文件描述符获取对应的设备状态,那什么是文件描述符对应的设备状态?

在一个进程中,一般会用到三个基本流,这三个基本流都可以被进程自动的调用,他们是:标准输入(STDIN_FILENO)、标准输出(STDOUT_FILENO)、标准出错(STDERR_FILEENO);在UNIX环境中,文件描述符0与标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错相关联。这是各种shell以及很多应用程序使用的惯例,如果你不按照这种惯例,那很多unix系统应用程序不能正常工作。

 

所以一般来说,会使用0、1、2三个文件描述符,除非你自己定义;

 

 

函数声明:

#include <termios.h>

#include <unistd.h>

1int tcgetattr(int fd,struct termios *termios_p)

用于获取文件描述符fd对应设备状态,然后置入termios_p所指向的结构体中;函数可以从后台进程中调用;但是,终端属性可能被后来的前台进程所改变

2int tcsetattrint fd,intoptional_actions,const struct termios *termios_p

设置文件描述符对应的设备状态;

optional_actions (tcsetattr函数的第二个参数)指定了什么时候改变会起作用:

TCSANOW:改变立即发生 

TCSADRAIN:改变在所有写入 fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变) 

TCSAFLUSH :改变在所有写入 fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。

 

(3)int tcsendbreak(int fd, int duration);

向fd发送0比特,持续时间为duration;如果终端使用异步串行数据传输的话。如果 duration 是 0,它至少传输 0.25 秒,不会超过 0.5 秒。如果duration 非零,它发送的时间长度由实现定义。如果终端并非使用异步串行数据传输,tcsendbreak() 什么都不做。

 

(4)int tcdrain(int fd);

挂起直到所有写入fd的输出全部发送完毕

 

 

(5)int tcflush(int fd, int queue_selector);

丢弃所有准备写入但还未发送给fd的数据或从fd已接收但还还未被读取的数据;丢弃对象取决于queue_selector

TCIFLUSH :刷新收到的数据但是不读 

TCOFLUSH :刷新写入的数据但是不传送 

TCIOFLUSH :同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送

 

(6)int tcflow(int fd, int action);

挂起fd发送操作或接收操作,挂起对象取决于action

TCOOFF :挂起输出 

TCOON :重新开始被挂起的输出 

TCIOFF :发送一个 STOP 字符,停止终端设备向系统传送数据 

TCION :发送一个 START 字符,使终端设备向系统传输数据 打开一个终端设备时的默认设置是输入和输出都没有挂起。

 

(7)void cfmakeraw(struct termios *termios_p);

设备终端属性,cfmakeraw设置终端属性为:

termios_p->c_iflag &=~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);

termios_p->c_oflag &= ~OPOST;

termios_p->c_lflag &=~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);

termios_p->c_cflag &=~(CSIZE|PARENB);

termios_p->c_cflag |= CS8;

 

 

(8)speed_t cfgetispeed(const struct termios *termios_p);

返回termios_p所指向结构体中的输入波特率

 

(9)speed_t cfgetospeed(const struct termios *termios_p);

返回termios_p所指向结构体中的输出波特率

 

(10)int cfsetispeed(struct termios *termios_p, speed_t speed);

设置termios_p所指向结构体中的输入波特率

 

(11)int cfsetospeed(struct termios *termios_p, speed_t speed);

设置termios_p所指向结构体中的输出波特率

 

(12)int cfsetspeed(struct termios *termios_p, speed_t speed);

 4.4BSD扩展,设置输入输出波特率

 

termios结构体定义为:

typedef unsigned char cc_t;

typedef unsigned int speed_t;

typedef unsigned int tcflag_t;

 

struct termios{

         tcflag_t    c_iflag;输入模式标志

         tcflag_t    c_oflag;输出模式标志

         tcflag_t    c_cflag;控制模式标志

         tcflag_t    c_lflag;本地模式标志

         cc_t           c_line;行控制

         cc_t           c_cc[NCCS];控制字符

#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1

#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1

}

 

c_iflag标志常量:

IGNBRK:忽略BREAK条件

BRKINT:如果设置IGNBRKBRKINT就会被忽略;如果仅仅设置了BRKINTBREAK将会丢弃输入和输出序列中的数据(flush),并且如果终端为前台进程组的控制终端,则BREAK将会产生一个SIGINT信号发送到这个前台进程组;如果IGNBRKBRKINT都未设置,BREAK将会被当做读入了NULL字节即’\0’,除非PARMRK设置了,在这种情况下,BREAK将会被当做读入了\377\0\0序列。

PARMRK:如果未设置IGNPAR标志,在带有奇偶校验错误或者帧错误的字符前使用\377\0\0来标志;如果IGNPARPARMRK均未设置,则将奇偶校验错误当做\0

ISTRIP:剥离第8bit

INLCR:将输入中的NL转换成CR

IGNCR :忽略输入中的回车

ICRNL :将输入中的CR转换为NL

IXON 允许输出端的XON/XOFF流控

IGNPAR 忽略帧错误和奇偶校验错误

INPCK 允许输入奇偶校验

IUCLC (非POSIX)将输入中的大写字符转换为小写

IXANY (XSI)任意击键将会重启已停止的输出(默认情况仅允许使用START字符来重启输出)

IXOFF 允许输入端XON/XOFF流控

IMAXBEL (非POSIX)输入队列满时响铃。Linux未实现此标志位,总是以此标志位被设置的情况动作

IUTF8 (从Linux 2.6.4开始支持,非POSIX)输入为UTF8编码

 

c_oflag标志常量:

OPOST 允许实现定义的输出处理

OLCUC (非POSIX)将输出中的小写字母映射为大写

ONLCR (XSI)将输出中的NL映射为CR-NL

OCRNL 将输出中的CR映射为NL

ONOCR 不在第零列输出CR

ONLRET 不输出CR

OFILL 发送填充字符实现延迟,而不是使用时间上的延迟

OFDEL (非POSIX)填充字符为ASCIIDEL(0177)。如果未设置,填充字符为ASCII NUL('\0')。(Linux中未实现)

NLDLY 新行延迟掩码。值为NL0和NL1。(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

CRDLY 回车(CR)延迟掩码。值为CR0,CR1,CR2,或CR3。(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

TABDLY 水平制表符延迟掩码。值为TAB0,TAB1,TAB2,TAB3(或XTABS)。值TAB3/XTABS表示将制表符扩展为空格(每8列为一个制表符停止位)。(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

BSDLY 回退符延迟掩码。值为BS0或BS1.(从未实现)(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

VTDLY 垂直制表符延迟掩码。值为VT0或VT1。

FFDLY 表单输入延迟掩码。值为FF0或FF1.(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

 

c_cflag标志常量:

CSIZE 字符尺寸掩码。值为CS5CS6CS7,或CS8

PARENB 允许输出端生产奇偶校验位,输入端进行校验

CBAUD (非POSIX)波特速率掩码(4+1比特)。(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

CBAUDEX (非POSIX)附加波特速率掩码(1比特),包含在CBAUD中。(需要_BSD_SOURCE或_SVID_SOURCE或_XOPEN_SOURCE)

CSTOPB 设置两个停止位(bits),而不是一位

CREAD 允许接收器

PARODD 如设置,则输入输出端奇偶校验为奇校验;未设置则为偶校验

HUPCL 上一次操作关闭设备后将调制解调器控制线设为低电平(挂起)

CLOCAL 忽略调制解调器控制线

LOBLK (非POSIX)阻塞非当前shell层输出。(Linux未实现)

CIBAUD (非POSIX)输入速率掩码。

CMSPAR (非POSIX)使用"stick"奇偶校验:如果设置了PARODD,将奇偶检验位总是置为1;如果未设置PARODD,奇偶校验位总是置为0.

CRTSCTS 允许RTS/CTS(硬件)流控。(需要_BSD_SOURCE或_SVID_SOURCE)

 

 

c_lflag标志常量:

ICANON 允许canonical模式

ECHO 回显所输入的字符

ECHONL 如果同时设置有ICANON标志,回显NL字符即使ECHO未设置

IEXTEN 允许实现所定义的输入处理。

ISIG 当接收到INTR/QUIT/SUSP/DSUSP字符,生成一个相应的信号

XCASE (非POSIX,Linux不支持)如果设置了ICANON,终端仅为大写字符模式。输入字符被转换为小写,除非以'\'开始;输出端,大写字符以'\'开始,小写字符被转换为大写

ECHOE 如果同时设置了ICANON标志,ERASE字符删除前一个所输入的字符,WERASE删除前一个输入的单词

ECHOK 如果同时设置有ICANON标志,KILL字符删除当前行

ECHOCTL (非POSIX)如果同时设置有ECHO标志,除TAB/NL/START/STOP外的ASCII控制字符将会被回显成'^X',其中X为控制符数值加0x40

ECHOPRT (非POSIX)如果同时设置有ICANON和IECHO,字符以已删除方式打印

ECHOKE (非POSIX)如果设置有ICANON,KILL以删除所在行所有字符方式显示

DEFECHO (非POSIX)仅当有进程读取时回显字符(Linux未实现)

FLUSHO (非POSIX,LINUX不支持)输出被丢弃。

NOFLSH 当生成SIGINT/SIGQUIT/SIGSUSP信号时禁止丢弃(flush)输入输出队列

TOSTOP 当后台进程试图写入自己的控制终端时,发送SIGTTOU信号给进程组

PENDIN (非POSIX,Linux不支持)

 

 

c_cc数组定义了一些特殊控制字符:

VMIN canonical模式读操作的最少字符数

VTIME canonical模式读操作超时(单位为1/10)

VINTR003,ETX,Ctrl-C,0177,DEL,rubout,中断字符。发送SIGINT信号。

VQUIT034,FS,Ctrl-\,退出字符。发送SIGQUIT信号。

VERASE0177,DEL,rubout,010,BS,Ctrl-H,#, 删除字符。删除上一个未删除的字符,但不删除前面的EOF或者行开始字符。

VKILL025,NAK,Ctrl-U,Cgtrl-X,@,Kill字符。删除自上一个EOF或行开始字符之后的所有输入字符。

VEOF004,EOT,Ctrl-D,文件结尾(End-of-file)。EOF将会让挂起的tty缓冲区内容发送给处于等待中的用户程序,而不用等待行结束标识(End-of-line)。

VEOL(0,NUL)额外的行结束符(End-of-line)

VEOL2(非POSIX;0,NUL)另一个行结束标识

VSWTCH(非POSIX;Linux不支持;0,NUL)切换字符

VSTART021,DC1,Ctrl-Q,开始字符。重启被STOP字符停止的输出

VSTOP023,DC3,Ctrl-S,停止字符。停止输出直到START

VSUSP032,SUB,Ctrl-Z,挂起字符。发送SIGSTP信号

VDSUSP(非POSIX;Linux不支持)031,EM,Ctrl-Y,延迟挂起字符:当用户程序读取字符时发送SIGTSTP信号

VLNEXT(非POSIX)026,SYN,Ctrl-V,标识下一个字符为字面意思而非可能的特殊控制含义

VWERASE(非POSIX)027,ETC,Ctrl-W,单词删除

VREPRINT(非POSIX)022,DC2,Ctrl-R,再次打印未读取字符

VDISCARD(非POSIX;Linux不支持)017,SI,Ctrl-O,开关切换:开始/停止丢弃挂起的输出

VSTATUS(非POSIX;Linux不支持)024,DC4,Ctrl-T,状态请求

 

 

获取/更改终端设置

tcgetattr(),tcsetattr()分别用于获取/更改终端设置:

 

tcgetattr()获取fd所指定终端的设置并存放入termios结构指针termios_p指向的地址空间;后台进程所获取的终端设置也可能随后被前台进程更改

 

tcsetattr()设置指定终端的属性。可选动作项指定终端属性何时更改:

TCSANOW 立即更改

TCSADRAIN当写入fd的所有输出发送完毕后更改

TCSAFLUSH所有写入fd的输出发送完毕,并且所有已接收但未读入的输入被丢弃后更改设置

 

 

Canonicak和non-canonical模式

canonical模式

 

输入工作在行模式。收到行定界符(NL,EOL,EOL2;或行首的EOF)后,输入行可供读取。read操作所读取的行内容包含行定界符。

允许行编辑(ERASE,KILL;如设置了IEXTEN标志,WERASE,REPRINT,LNEXT)。

non-canonical模式下,无需用户输入行定界符,输入立即可读取。

 

c_cc[VTIME]和c_cc[VMIN]对read操作的影响:

MIN==0;TIME==0:如有数据可用,read立即返回min(请求数量,可用字符数量)个字符;如无数据可用,read返回0

MIN>0;TIME==0:read阻塞,直到至少有min(请求数量,MIN)个字符可用,read返回两值中较小的一个

MIN==0;TIME>0:TIME指定读取超时(单位为1/10秒)。当调用read时设定定时器。当至少有一个字符可用或超时后,read返回。如果在超时前无可用字符,read返回0

MIN>0;TIME>0:TIME指定读取超时,收到输入的第一个字符后重启定时器。read在读取到MIN和所请求数量两者中较少的字符,或超时后,返回。至少会读取到一个字符。

 

RAW模式

cfmakeraw()设置终端工作在raw模式下:输入以字符方式提供,禁止回显,所有特殊字符被禁止。

这种模式下终端属性为:

termios_p->c_iflag &=~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);

termios_p->c_oflag &= ~OPOST;

termios_p->c_lflag &=~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);

termios_p->c_cflag &=~(CSIZE|PARENB);

termios_p->c_cflag |= CS8;

 

 

在FFMPEG中就是应用了RAW模式;

 

 

 

 

 

 

 

 

 

 

 

isatty()函数

 

本程序中所用的另外一个函数是isatty()函数;

 

#include <unistd.h>

int isatty(int filedes){

         structtermios ts;

         return(tcgetattr(fd,&ts) != -1);

}

fd为文件描述符;上面isatty函数的实现只用了一个终端专用的函数tcgetattr(),如果成功执行,它没有改变任何事情,并且可对终端进行判断;

返回值:若为终端设备则返回1(真),否则返回0;

 

关于程序中isatty(0)和isatty(2)的解释:

在一个进程中,一般会用到三个基本流,这三个基本流都可以被进程自动的调用,他们是:标准输入(STDIN_FILENO)、标准输出(STDOUT_FILENO)、标准出错(STDERR_FILEENO);在UNIX环境中,文件描述符0与标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错相关联。这是各种shell以及很多应用程序使用的惯例,如果你不按照这种惯例,那很多unix系统应用程序不能正常工作。

 

所以程序中的0和2应该替换成符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILEENO

,以用来判别文件描述符是否为终端机。

 

00289 #if HAVE_ISATTY

00290        istty = isatty(0) && isatty(2);

00291 #endif

00292        if (istty && tcgetattr (0, &tty)== 0) {

 

通过isstty的值来判断是否为标准输入和标准出错;然后置入termios型的结构体tty中;函数可以从后台进程中调用;

这样判断之后,才能设置termios等属性;

 

 

 

signal()函数:

 

在程序中会时常见到signal这个函数,那就来看一下这个函数的作用:

 

Linux中的信号(signal)全称为:软中断信号,又称为软中断,常被用作进程之间进行简单通信,或系统内核用来通知进程某个事件的发生。一般情况下,进程仅能从信号中获知信号编号和少量其他信息(如信号发送者的真实用户ID/内存异常号发生的地址/文件描述符等)

 

(1)信号类型:linux系统中,支持POSIX标准的规则信号(regular signal,编号1-31)和实时信号(real-time,编号32-63)。对于regular信号,无论发送多少次,在接手进程处理之前,重复的信号会被合并为一个(每一种regular signal对应于系统进程表项中软中断字段的一个比特,因此不同的喜好可以同时存在,同一个信号仅能表示有或无而不能表示重复的次数);而real-time signal发送多少次,就会在接收进程的 信号队列中出现多少次。

 

Linuxi386上的31个规则信号(regular signal)

编号

信号名称

缺省动作

说明

1

SIGHUP

终止

终止控制终端或进程

2

SIGINT

终止

键盘产生的中断(Ctrl-C)

3

SIGQUIT

dump

键盘产生的退出

4

SIGILL

dump

非法指令

5

SIGTRAP

dump

debug中断

6

SIGABRTSIGIOT

dump

异常中止

7

SIGBUSSIGEMT

dump

总线异常/EMT指令

8

SIGFPE

dump

浮点运算溢出

9

SIGKILL

终止

强制进程终止

10

SIGUSR1

终止

用户信号,进程可自定义用途

11

SIGSEGV

dump

非法内存地址引用

12

SIGUSR2

终止

用户信号,进程可自定义用途

13

SIGPIPE

终止

向某个没有读取的管道中写入数据

14

SIGALRM

终止

时钟中断(闹钟)

15

SIGTERM

终止

进程终止

16

SIGSTKFLT

终止

协处理器栈错误

17

SIGCHLD

忽略

子进程退出或中断

18

SIGCONT

继续

如进程停止状态则开始运行

19

SIGSTOP

停止

停止进程运行

20

SIGSTP

停止

键盘产生的停止

21

SIGTTIN

停止

后台进程请求输入

22

SIGTTOU

停止

后台进程请求输出

23

SIGURG

忽略

socket发生紧急情况

24

SIGXCPU

dump

CPU时间限制被打破

25

SIGXFSZ

dump

文件大小限制被打破

26

SIGVTALRM

终止

虚拟定时时钟

27

SIGPROF

终止

profile timer clock

28

SIGWINCH

忽略

窗口尺寸调整

29

SIGIO/SIGPOLL

终止

I/O可用

30

SIGPWR

终止

电源异常

31

SIGSYSSYSUNUSED

dump

系统调用异常

 

标号为0的信号,用以测试进程是否拥有信号发送的权限,并不会被实际发送。

同一信号在不同的系统中的值可能不一样,所以最好使用信号名而不是信号值。

信号值越小,优先级越高。

(2)信号处理:

通常,进程对信号的处理方式可以是下列三种方式之一:默认方式(default,交由系统默认信号处理);忽略(ignore,不做任何处理);进程捕获信号并处理(capture)。

 

注意:SIGKILLSIGSTOP不能被用户程序捕获,也不能被忽略。也就是说,SIGKILLSIGSTOP总是会由系统默认的处理函数进行处理(最终结果是进程被终止)

 

(3)信号相关系统调用函数

函数原型:

Void*signal(int signum,void(*handler)(int)))(int;

或者是:

typedef void(*sig_t)(int)

sig_t signal(int signum,sig_t handler);

函数参数:

第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILLSIGSTOP之外的任何一种信号。

第二个参数handler描述了与信号关联的动作,它可以取以下三种值:

(1)一个无返回值的函数地址:

此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为sig的信号时,就执行了handler所指定的函数。这个函数应有如下形式的定义:

Void funcint sig);

Sig是传递给它的唯一参数。执行了signal()调用后,进程耗子药接收到类型为sig的信号,不管其在执行程序的哪一部分,就立即执行func()函数。当func函数执行完毕后,控制权才返回原来进程中被中断的那一点继续执行。

 

(2)SIG_IGN

这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。

 

(3)SIG_DFL

这个符号表示恢复系统对信号的默认处理。

 

函数说明:

Signal()函数会依参数signum指定的信号编号来设置信号的处理函数。当指定的信号到达时,就会跳到参数handler指定的函数执行。

 

返回值:

返回先前信号处理的函数指针,如果有错误则返回SIG_ERR(-1)

注:如果信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式。

 

 

对应于本程序中的signal信号来说:

 

00308         signal(SIGQUIT, sigterm_handler); /* Quit (POSIX).  */POSIX信号,键盘产生退出

00313     signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */ANSI信号:中断信号

00314     signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */ANSI信号:进程终止

00315 #ifdef SIGXCPU

00316     signal(SIGXCPU, sigterm_handler);ANSI信号:CPU时间限制被打破

 

 

 

 

 

 

 

13、parse_cpuflags()函数:cmdutils.c.

 

03145     parse_cpuflags(argc, argv, options);

 

本函数主要是解析一些CPU标志信息

 

具体为:

03109 static void parse_cpuflags(int argc, char **argv, const OptionDef *options)

03110 {

03111     int idx = locate_option(argc, argv, options, "cpuflags");确定参数选项cpuflags的位置

03112     if (idx && argv[idx + 1])

03113         opt_cpuflags(NULL"cpuflags", argv[idx + 1]);如果存在参数选项cpuflags,返回一些具体的CPUflags

03114 }

 

本函数先检测出一些本地CPU架构支持的一些信息,然后根据命令行参数来确定具体的CPU标志信息,然后根据命令行参数解析出来的CPU标志,强制本地CPU使用这些标志信息:

下面看一下opt_cpuflags()函数的内容:

00566 int opt_cpuflags(void *optctx, const char *opt, const char *arg)

00567 {

00568     int ret;

00569     unsigned flags = av_get_cpu_flags();Return the flags which specify extensions supported by the CPU.返回一些标识信息,具体是指cpu支持的一些特定的扩展架构,例如ARMPPCX86等架构;这个函数也是只检查一次,通过checked变量来设定只检查一次。

00570 

00571     if ((ret = av_parse_cpu_caps(&flags, arg)) < 0)parse CPU caps from a string and update the given AV_CPU_* flags based on that.从命令行参数字符串arg中解析CPU的信息,并且以此为基础更新给定的AV_CPU_*标志信息。

00572         return ret;

00573 

00574     av_force_cpu_flags(flags);Disables cpu detection and forces the specified flags.

-1 is a special case that disables forcing of specific flags.关闭cpu检测,并且强制使用特定的flags,这里强制使用的flags就是上边av_get_cpu_flags()函数里检测出来的flags;如果强制使用不成功则返回-1

00575     return 0;

00576 }

(1)下面是具体的av_get_cpu_flags()

本函数主要是通过检测CPU,来获得本地CPU架构支持的一些标志信息

00030 int av_get_cpu_flags(void)

00031 {

00032     if (checked)利用变量checked来限制检测次数,只能检测一次;如果检测成功,则checked变量被赋值1,再次检测时就直接返回;

00033         return flags;

00034 

00035     if (ARCH_ARM) flags = ff_get_cpu_flags_arm();返回ARM架构的CPU标志信息

00036     if (ARCH_PPC) flags = ff_get_cpu_flags_ppc();返回PPC架构的CPU标志信息

00037     if (ARCH_X86) flags = ff_get_cpu_flags_x86();返回X86架构的CPU标志信息

00038 

00039     checked = 1;

00040     return flags;

00041 }

 

(2)下面是具体的av_parse_cpu_caps()信息:

根据已经设定好的CPU的信息,进行匹配获得具体的CPU信息。

00114 int av_parse_cpu_caps(unsigned *flagsconst char *s)

00115 {

00116         static const AVOption cpuflags_opts[] = {这个是AVOption类型的数组cpuflags_opts[],里面是一些cpuflag信息,下面有AVOption的具体信息

00117         { "flags"   , NULL, 0, AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT64_MIN, INT64_MAX, .unit = "flags" },

00118 #if   ARCH_PPC//这是PPC架构下的CPU信息

00119         { "altivec" , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_ALTIVEC  },    .unit = "flags" },

00120 #elif ARCH_X86//X86架构下的CPU信息

00121         { "mmx"     , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_MMX      },    .unit = "flags" },

00122         { "mmx2"    , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_MMX2     },    .unit = "flags" },

00123         { "mmxext"  , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_MMX2     },    .unit = "flags" },

00124         { "sse"     , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE      },    .unit = "flags" },

00125         { "sse2"    , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE2     },    .unit = "flags" },

00126         { "sse2slow"NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE2SLOW },    .unit = "flags" },

00127         { "sse3"    , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE3     },    .unit = "flags" },

00128         { "sse3slow"NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE3SLOW },    .unit = "flags" },

00129         { "ssse3"   , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSSE3    },    .unit = "flags" },

00130         { "atom"    , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_ATOM     },    .unit = "flags" },

00131         { "sse4.1"  , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE4     },    .unit = "flags" },

00132         { "sse4.2"  , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_SSE42    },    .unit = "flags" },

00133         { "avx"     , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_AVX      },    .unit = "flags" },

00134         { "xop"     , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_XOP      },    .unit = "flags" },

00135         { "fma4"    , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_FMA4     },    .unit = "flags" },

00136         { "3dnow"   , NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_3DNOW    },    .unit = "flags" },

00137         { "3dnowext"NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_3DNOWEXT },    .unit = "flags" },

00138 #elif ARCH_ARM//ARM下的CPU参数

00139         { "armv5te",  NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_ARMV5TE  },    .unit = "flags" },

00140         { "armv6",    NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_ARMV6    },    .unit = "flags" },

00141         { "armv6t2",  NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_ARMV6T2  },    .unit = "flags" },

00142         { "vfp",      NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_VFP      },    .unit = "flags" },

00143         { "vfpv3",    NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_VFPV3    },    .unit = "flags" },

00144         { "neon",     NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_CPU_FLAG_NEON     },    .unit = "flags" },

00145 #endif

00146         { NULL },

00147     };

00148     static const AVClass class = {

00149         .class_name = "cpuflags",标志的名字

00150         .item_name  = av_default_item_name,标志所属项目的名字

00151         .option     = cpuflags_opts,标志所属选项的名字

00152         .version    = LIBAVUTIL_VERSION_INT,所属版本种类

00153     };

00154     const AVClass *pclass = &class;

00155 

00156     return av_opt_eval_flags(&pclass, &cpuflags_opts[0], s, flags);

00157 }

AVOption结构的具体信息为:

00246 typedef struct AVOption {

00247     const char *name;参数选项的名字

00248 

00253     const char *help;辅助信息

00254 

00259     int offset;偏移

00260     enum AVOptionType type;枚举类型的选项类型,主要有:

AV_OPT_TYPE_FLAGS 

AV_OPT_TYPE_INT 

AV_OPT_TYPE_INT64 

AV_OPT_TYPE_DOUBLE 

AV_OPT_TYPE_FLOAT 

AV_OPT_TYPE_STRING 

AV_OPT_TYPE_RATIONAL 

AV_OPT_TYPE_BINARY  offset must point to a pointer immediately followed by an int for the length

AV_OPT_TYPE_CONST 

AV_OPT_TYPE_IMAGE_SIZE  offset must point to two consecutive integers

AV_OPT_TYPE_PIXEL_FMT 

FF_OPT_TYPE_FLAGS 

FF_OPT_TYPE_INT 

FF_OPT_TYPE_INT64 

FF_OPT_TYPE_DOUBLE 

FF_OPT_TYPE_FLOAT 

FF_OPT_TYPE_STRING 

FF_OPT_TYPE_RATIONAL 

FF_OPT_TYPE_BINARY  offset must point to a pointer immediately followed by an int for the length

FF_OPT_TYPE_CONST 

00261 

00265     union {

00266         int64_t i64;

00267         double dbl;

00268         const char *str;

00269         /* TODO those are unused now */

00270         AVRational q;

00271     } default_val;标量选项的默认值

00272     double min;   参数选项的最小正确值              

00273     double max;   参数选项的最大正确值

00274 

00275     int flags;下面就是指的具体的标志信息

00276 #define AV_OPT_FLAG_ENCODING_PARAM  1   

00277 #define AV_OPT_FLAG_DECODING_PARAM  2   

00278 #define AV_OPT_FLAG_METADATA        4   

00279 #define AV_OPT_FLAG_AUDIO_PARAM     8

00280 #define AV_OPT_FLAG_VIDEO_PARAM     16

00281 #define AV_OPT_FLAG_SUBTITLE_PARAM  32

00282 #define AV_OPT_FLAG_FILTERING_PARAM (1<<16) 

00283 //FIXME think about enc-audio, ... style flags

00284 

00290     const char *unit;该参数选项属于哪个逻辑上的单位

00291 } AVOption;

 

14、parse_options()函数:cmdutils.c.

本函数的功能主要是解析参数选项:

03147     /* parse options */

03148     parse_options(&o, argc, argv, optionsopt_output_file);

函数原型为:

 

void parse_options ( void *  optctx,

int  argc,

char **  argv,

const OptionDef *  options,

void(*)(void *, const char *)  parse_arg_function  

)

传入的参数为:

Void*optctx OptionContext o

Argc为参数的个数

Argv为具体的参数字符串数组

Options为已经定义好的const型参数选项数组,这个是拿来和给定的参数作比较的

parse_arg_function函数指针指向void opt_output_file(void * optctx,const char * filename) 函数

具体分析如下:

 

00332 void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,

00333                    void (*parse_arg_function)(void *, const char*))

00334 {

00335     const char *opt;

00336     int optindex, handleoptions = 1, ret;

00337 

00338     /* perform system-dependent conversions for arguments list */执行参数列表的系统相关的转换;但是打开一看,什么都没有,原来是为以后准备用的;这是个内联函数

00339     prepare_app_arguments(&argc, &argv);

00340 

00341     /* parse options */这才是真正的解析参数选项的程序:

00342     optindex = 1;

00343     while (optindex < argc) {这个是必须的,索引号当然要小于参数选项的个数了

00344         opt = argv[optindex++];字符型指针opt指向每一个参数选项字符串的首地址

00345 

00346         if (handleoptions && opt[0] == '-' && opt[1] != '\0') {当前解析的参数选项

00347             if (opt[1] == '-' && opt[2] == '\0') {这个是用来跳出一些误写的--的参数选项,继续解析后面的参数选项,在解析每一个参数选项时都要进行这样一个判断

00348                 handleoptions = 0;

00349                 continue;

00350             }

00351             opt++;指向每一个参数选项首地址的指针后移,指向下一个待解析的参数选项

00352 

00353             if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)Parse one given option.

Returns:

on success 1 if arg was consumed, 0 otherwise; negative number on error

解析给定的参数选项,正确返回1,错误返回负值

00354                 exit_program(1);

00355             optindex += ret;解析正确则索引号加1

00356         } else {

00357             if (parse_arg_function)

这地方的parse_arg_function()函数就是void opt_output_file(void * optctx,

const char * filename 

);功能是解析输出文件的格式

00358                 parse_arg_function(optctx, opt);这里根据函数指针调用opt_output_file()函数,解析输出文件的一些信息;传入的参数为:OptionContext oopt(指向每一个参数选项字符串的首地址);

00359         }

00360     }

00361 }

 

 

先来看parse_option()函数,然后看parse_arg_function()

 

(1)下面是具体的parse_option()函数分析:

函数原型为:

 

int parse_option

(

void * 

optctx,

   

const char * 

opt,

   

const char * 

arg,

   

const OptionDef * 

options

 

 

)

     

Parse one given option.

Returns:

on success 1 if arg was consumed, 0 otherwise; negative number on error

Definition at line 261 of file cmdutils.c.

 

函数功能:解析函给定的参数选项

00261 int parse_option(void *optctx, const char *opt, const char *arg,

00262                  const OptionDef *options)

00263 {

00264     const OptionDef *po;

00265     int bool_val = 1;

00266     int *dstcount;

00267     void *dst;

00268 

00269     po = find_option(options, opt);根据给定的参数,就是根据opt指向的参数了,找到每一个参数在静态OptionDef型数组options里的位置,并返回这个位置。

00270     if (!po->name && opt[0] == 'n' && opt[1] == 'o') {

00271         /* handle 'no' bool option */处理no这个bool型参数选项

00272         po = find_option(options, opt + 2);如果有no的话,po的位置向后移两位

00273         if ((po->name && (po->flags & OPT_BOOL)))

00274             bool_val = 0;

00275     }

00276     if (!po->name)

00277         po = find_option(options, "default");

00278     if (!po->name) {

00279         av_log(NULLAV_LOG_ERROR"Unrecognized option '%s'\n", opt);

00280         return AVERROR(EINVAL);

00281     }

00282     if (po->flags & HAS_ARG && !arg) {

00283         av_log(NULLAV_LOG_ERROR"Missing argument for option '%s'\n", opt);

00284         return AVERROR(EINVAL);

00285     }

00286 

00287     /* new-style options contain an offset into optctx, old-style address of

00288     * a global var*/如果这个选项在options出现过并且是OPT_OFFSET或者是OPT_SPEC的,则dst指向该参数在optctx中偏移地址(uint8_t *)optctx + po->u.off;如果不是OPT_OFFSETOPT_SPEC型的,则dst指向该选项的地址po->u.dst_ptr

00289     dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? (uint8_t *)optctx + po->u.off

00290                                               : po->u.dst_ptr;

00291 

00292     if (po->flags & OPT_SPEC) {

00293         SpecifierOpt **so = dst;如果是OPT_SPEC型的flags,则将上面的偏移地址存放在二级指针so中;其中so指向存放数个偏移地址的空间的首地址,而*so指向存放每个偏移地址的空间的首地址,通过*so++可以访问每一个偏移地址,**so指向偏移地址的实际内容,通过**so++可以访问偏移地址的具体内容;使用二级指针是为了便于处理,这里才感觉到C语言指针的强大,能够如此灵活的运用指针来进行字符串的处理,羡慕啊;FFMPEG中很多这样的技巧;

00294         char *p = strchr(opt, ':');

00295 

00296         dstcount = (int *)(so + 1);so+1跳过存放当前偏移地址的这一段存储空间,然后指向存放下一个存放偏移地址的空间的首地址,下一个偏移地址即写到disconunt指向的空间;然后注意此时将地址强制转化为int*型,是为了下面为新偏移地址分配空间

00297         *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1);下面是具体内容

void* grow_array(void * array,

int elem_size,

int * size,

int new_size 

)

Realloc array to hold new_size elements of elem_size.对应于此处,即为so分配新的空间,以存放新的偏移地址,并将新空间的首地址赋给*so,分配的空间的大小为*discount+1,为什么要加1呢,是因为为了

Calls exit_program() on failure.

Parameters:

elem_size size in bytes of each element

size new element count will be written here

 

00298         (*so)[*dstcount - 1].specifier = av_strdup(p ? p + 1 : "");

00299         dst = &(*so)[*dstcount - 1].u;

00300     }

00301 下面是针对不同flags类型的数据,进行不同的处理,然后除了字符串类型的参数选项,一般将字符串转化为数字,然后存放在dst中,但是不大明白什么意思

00302     if (po->flags & OPT_STRING) {

00303         char *str;

00304         str = av_strdup(arg);

00305 //         av_freep(dst);

00306         *(char **)dst = str;

00307     } else if (po->flags & OPT_BOOL) {

00308         *(int *)dst = bool_val;

00309     } else if (po->flags & OPT_INT) {

00310         *(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX);

00311     } else if (po->flags & OPT_INT64) {

00312         *(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX);

00313     } else if (po->flags & OPT_TIME) {

00314         *(int64_t *)dst = parse_time_or_die(opt, arg, 1);

00315     } else if (po->flags & OPT_FLOAT) {

00316         *(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITYINFINITY);

00317     } else if (po->flags & OPT_DOUBLE) {

00318         *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITYINFINITY);

00319     } else if (po->u.func_arg) {

00320         int ret = po->u.func_arg(optctx, opt, arg);如果正确的话,一般返回0

00321         if (ret < 0) {

00322             av_log(NULLAV_LOG_ERROR,

00323                    "Failed to set value '%s' for option '%s'\n", arg, opt);

00324             return ret;

00325         }

00326     }

00327     if (po->flags & OPT_EXIT)

00328         exit_program(0);

00329     return !!(po->flags & HAS_ARG);返回01

00330 }

 

 

1Find_option()函数:

 

 

 

 

 

static const OptionDef* find_option

(

const OptionDef * 

po,

   

const char * 

name

 

 

)

函数参数:

Const *name就是具体的每一个参数选项的首地址

OptionDef* po就是const型的OptionDef型数组,已经设定好了

函数功能:

猜测功能就是将给定的参数选项与已知的参数选项数组进行对比,找到匹配项,然后返回给定参数选项在已知OptionDef型数组中位置。

 

 

 

 

 

 

 

 

2)现在看一下const OptionDef型数组options的具体内容:

 

 

 

04674 static const OptionDef options[] = {

根据OptionDef的定义可知:options数组的内容依次为:

选项名称;选项标志信息(即选项类型);函数指针调用的函数;选项功能的解释

04675 #include "cmdutils_common_opts.h"

04676     { "n"OPT_BOOL, {(void *)&no_launch }, "enable no-launch mode" },

04677     { "d", 0, {(void*)opt_debug}, "enable debug mode" },

04678     { "f"HAS_ARG | OPT_STRING, {(void*)&config_filename }, "use configfile instead of /etc/ffserver.conf""configfile" },

04679     { NULL },

04680 };

cmdutils_common_opts.h的具体内容为:

00001     { "L"          , OPT_EXIT, {.func_arg = show_license},      "show license" },

00002     { "h"          , OPT_EXIT, {.func_arg = show_help},         "show help""topic" },

00003     { "?"          , OPT_EXIT, {.func_arg = show_help},         "show help""topic" },

00004     { "help"       , OPT_EXIT, {.func_arg = show_help},         "show help""topic" },

00005     { "-help"      , OPT_EXIT, {.func_arg = show_help},         "show help""topic" },

00006     { "version"    , OPT_EXIT, {.func_arg = show_version},      "show version" },

00007     { "formats"    , OPT_EXIT, {.func_arg = show_formats  },    "show available formats" },

00008     { "codecs"     , OPT_EXIT, {.func_arg = show_codecs   },    "show available codecs" },

00009     { "decoders"   , OPT_EXIT, {.func_arg = show_decoders },    "show available decoders" },

00010     { "encoders"   , OPT_EXIT, {.func_arg = show_encoders },    "show available encoders" },

00011     { "bsfs"       , OPT_EXIT, {.func_arg = show_bsfs     },    "show available bit stream filters" },

00012     { "protocols"  , OPT_EXIT, {.func_arg = show_protocols},    "show available protocols" },

00013     { "filters"    , OPT_EXIT, {.func_arg = show_filters  },    "show available filters" },

00014     { "pix_fmts"   , OPT_EXIT, {.func_arg = show_pix_fmts },    "show available pixel formats" },

00015     { "layouts"    , OPT_EXIT, {.func_arg = show_layouts  },    "show standard channel layouts" },

00016     { "sample_fmts"OPT_EXIT, {.func_arg = show_sample_fmts }, "show available audio sample formats" },

00017     { "loglevel"   , HAS_ARG,  {.func_arg = opt_loglevel},      "set libav* logging level""loglevel" },

00018     { "v",           HAS_ARG,  {.func_arg = opt_loglevel},      "set libav* logging level""loglevel" },

00019     { "debug"      , HAS_ARG,  {.func_arg = opt_codec_debug},   "set debug flags""flags" },

00020     { "fdebug"     , HAS_ARG,  {.func_arg = opt_codec_debug},   "set debug flags""flags" },

00021     { "report"     , 0,        {(void*)opt_report}, "generate a report" },

00022     { "max_alloc"  , HAS_ARG,  {.func_arg = opt_max_alloc},     "set maximum size of a single allocated block""bytes" },

00023     { "cpuflags"   , HAS_ARG | OPT_EXPERT, {.func_arg = opt_cpuflags}, "force specific cpu flags""flags" },

 

 

 

 

3)下面看一下

 

double parse_number_or_die

(

const char * 

context,

   

const char * 

numstr,

   

int 

type,

   

double 

min,

   

double 

max

 

 

)

     

Parse a string and return its corresponding value as a double.

Exit from the application if the string cannot be correctly parsed or the corresponding value is invalid.

Parameters:

 

context 

the context of the value to be set (e.g. the corresponding command line option name)

 

numstr 

the string to be parsed

 

type 

the type (OPT_INT64 or OPT_FLOAT) as which the string should be parsed

 

min 

the minimum valid accepted value

 

max 

the maximum valid accepted value

 

对应此处,此

函数传入的参数为:

Contextopt

Numstrarg

TypeOPT_*(OPT_INT64OPT_FLOAT,OPT_DOUBLE)

 

 

 

(2)现在应该看parse_arg_function()函数了:

 

00357             if (parse_arg_function)

00358                 parse_arg_function(optctx, opt);

上面说了函数指针parse_arg_function指向void opt_output_file(void *optctx, const char *filename)

 

现在来看opt_output_file()函数:

由于这个函数比较大,所以只看视频处理的,其他的不看了:

先看参数吧:

OptctxOptionContext o

Filenameopt,具体是指每一个参数选项的首地址;

具体程序如下:

01420 void opt_output_file(void *optctx, const char *filename)

01421 {

01422     OptionsContext *o = optctx;从main()的第一句就进行初始化的结构体,终于用到了

01423     AVFormatContext *oc;容器结构体终于出来了,

01424     int i, j, err;

01425     AVOutputFormat *file_oformat;复用器muxer的抽象结构体

01426     OutputStream *ost;输出流的结构体

01427     InputStream  *ist;输入流的结构体

 

 

 

1)OutputStream结构体中重要的成员变量:

Int file_index:多媒体文件的索引号

Int index:数据流在输出文件中的索引号

Source_index:输入数据流的索引号

 

AVStream *st:输出文件数据流的抽象

AVCodec *enc:编码器的抽象

AVFrame *filtered_frame:经过滤波器的数据帧

 

 

Video only

AVRational frame_rate:帧率

Int force_fps:强制性帧率

Int64_t *forced_kf_pts:强制关键帧的pts

Int forced_kf_count:强制性的关键帧的计数器

Int forced_kf_index:强制性的关键帧的索引号

Char* forced_keyframes:强制性的关键帧

 

Int fininshed:写数据完毕的标志位

Int unavailable:数据流不可用的标志位

Int Stream_copy:数据流copy的标志位

 

 

 

2)InputStream结构体中重要的成员变量:

Int file_index:多媒体文件的索引号

AVStream *st:输入文件中数据流的抽象

int discard:如果数据流数据将要丢弃,则此标志位为真

int decoding_needed:如果数据包必须以”raw_fifo”模式解码,则此标志位为真

 

 

AVCodec *dec:解码器

AVFrame *decoded_frame:解码帧

int64_t start:读数据开始的时间

int64_t next_dts:下一帧的dts,如果一个数据包中包含多个数据帧,则用来指当前数据包中当前帧的下一帧

int64_t dts:当前数据帧的dts

int64_t next_pts:对比上边的dts

int64_t pts:当前数据帧的pts

 

FrameBuffer *buffer_pool:解码数据的缓冲池

 

 

 

 

 

 

3)AVFormatContext结构体中重要的成员变量:

const AVClass *av_class:日志和AVOptions的类

struct AVInputFormat* iformat:复用器muxer,注意,muxerdemuxer同时只能存在一个

struct AVOutputFormat* oformat:解复用器demuxer

void* priv_data:容器的私有数据

AVIOContext* pbI/O上下文信息

int ctx_flags:容器特定的标志,即AVFMTCTX_*

unsigned int nb_streams:文件中所有数据流的列表

AVStream** streams:数据流的抽象,二级指针,用以存放多个数据流,其中**streams指向每一个数据流具体内容,*streams指向每一个数据流的首地址,streams指向数据流串的首地址

char* filename[1024]:输入或输出的文件名

int64_t start_time:解码时用:数据流中第一帧在时间轴上的位置,以AV_TIME_BASE为单位,微秒级

int64_t duration:解码时用:数据流的持续时间,以AV_TIME_BASE为单位,微秒级

int bit_rate:解码时用:整个数据流的比特率,以bit/s为单位,若不可用,则为0

unsigned int packet_size:数据包的尺寸

int max_delay:最大的停顿,是指在最后期限之前已经完成编解码,这段空余的时间

int flags:一些标志信息,包括:

00972 #define AVFMT_FLAG_GENPTS       0x0001 产生pts

00973 #define AVFMT_FLAG_IGNIDX       0x0002 忽略index

00974 #define AVFMT_FLAG_NONBLOCK     0x0004 不分块

00975 #define AVFMT_FLAG_IGNDTS       0x0008 忽略dts

00976 #define AVFMT_FLAG_NOFILLIN     0x0010 不填充,不填充什么呢

00977 #define AVFMT_FLAG_NOPARSE      0x0020 不解析,不解析什么呢

00978 #define AVFMT_FLAG_NOBUFFER     0x0040 没有缓冲

00979 #define AVFMT_FLAG_CUSTOM_IO    0x0080 自定义IO

00980 #define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 丢弃破坏了的数据

00981 #define AVFMT_FLAG_MP4A_LATM    0x8000 MP4相关的,不懂

00982 #define AVFMT_FLAG_SORT_DTS    0x10000 选择的dts

00983 #define AVFMT_FLAG_PRIV_OPT    0x20000 私有的参数选项

00984 #define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 保持side_data的标志

unsigned int probesize:解码时用:将要探测的数据的尺寸

int max_analyse_duration:解码时用:输入数据流在avformat_find_stream_info()函数里分析时所用的最大时间,以AV_TIME_BASE为单位

enum AVCodecID video_codec_id:强制性的视频编解码器的id

 

int max_chunk_size:数据块最大的生存时间,以微秒为单位

struct AVPacketList* packet_buffer:当数据包已经缓冲完毕但是还没有解码时,使用这个缓冲区域;

struct AVPacketList *  packet_buffer_end:什么意思?

/* av_seek_frame() support */

int64_t data_offset:第一个数据包的偏移,为啥是64位整型呢

struct AVPakcetList* raw_packet_buffer:来自解复用器的原始数据包,在解析和解码之前存在

struct AVPacketList *  raw_packet_buffer_end:什么意思?

struct AVPacketList* parse_queue:数据包经解析器划分后,在这个数据结构中成为队列

struct AVPacketList *parse_queue_end:什么意思?

 

4)AVOutputFormat结构体中重要成员变量:

const char *  name:复用器的名字

const char *  long_name:复用器的长名字,对输出格式的描述性名字,意味着更加人性化的名字

const char *  mime_type:什么意思?

/* output support*/

enum AVCodecID  video_codec:默认的视频编解码器default video codec 

int  flags

AVFMT_NOFILE, 没有文件

AVFMT_NEEDNUMBER, 需要数字

AVFMT_RAWPICTURE, 原始图像

AVFMT_GLOBALHEADER, 全局头部

AVFMT_NOTIMESTAMPS, 没有时间戳

AVFMT_VARIABLE_FPS, 帧率可变的

AVFMT_NODIMENSIONS, 没有维度信息

AVFMT_NOSTREAMS, 没有数据流信息

AVFMT_ALLOW_FLUSH, 允许刷新

AVFMT_TS_NONSTRICT 不严格的ts

struct AVOutputFormat *  next指向下一个解复用器,可以构成demuxer链表形式

int  priv_data_size私有数据的尺寸,以便能够加到数据包里面

 下面是demuxer的接口:

int(*  write_header )(struct AVFormatContext *)写头部信息的接口

int(*  write_packet )(struct AVFormatContext *, AVPacket *pkt)写数据包的接口

int(*  write_trailer )(struct AVFormatContext *)写尾部信息

int(*  interleave_packet )(struct AVFormatContext *, AVPacket *out, AVPacket *in, int flush)当前只有当像素格式不是YUV420p时,才能被用来设置像素格式

  Currently only used to set pixel format if not YUV420P. 

int(*  query_codec )(enum AVCodecID id, int std_compliance)如果给定的编解码器可以存放在这个容器里,那么使用这个接口函数检测一下这些编解码器

  Test if the given codec can be stored in this container. 

void(* get_output_timestamp )(struct AVFormatContext *s, int stream, int64_t *dts, int64_t *wall)得到输出数据流的时间戳

 

 

 

知道了上述结构体中的重要的变量后,那就来看一下具体的opt_output_file()

 

----------------------------------------以下是opt_output_file()函数的分析----------------------------------------------------------------------------------------------------

 

01429     if (configure_complex_filters() < 0) {配置复杂的滤波器模式
01430         av_log(NULL, AV_LOG_FATAL, "Error configuring filters.\n");
01431         exit_program(1);
01432     }
我们知道ffmpeg滤波器模式有两种:

 

一种是simple,一种是complex,分别来看一下:

先看simple:

 

3.1.1 Simple filtergraphs

 

Simple filtergraphs are those that have exactly one input and output, both of the same type. In the above diagram they can be represented by simply inserting an additional step between decoding and encoding:

简单的滤波器图谱就是指,只有一个输入和输出,并且两种是相同的类型;他们可以通过在解码和编码之间简单的插入一个额外的步骤来表示,如下图:

 
 _________                        __________              ______________
|         |                      |          |            |              |
| decoded |  simple filtergraph  | filtered |  encoder   | encoded data |
| frames  | -------------------> | frames   | ---------> | packets      |
|_________|                      |__________|            |______________|

Simple filtergraphs are configured with the per-stream ‘-filter’ option (with ‘-vf’ and ‘-af’ aliases for video and audio respectively). A simple filtergraph for video can look for example like this:

简单的滤波器图谱可以通过'-filter'选项来设置每一个数据流(还有'-vf'和'-af'可选,分别用来选择是视频还是音频采用这种模式);简单的滤波器模式对于视频来说,可以用下图来表示:

输入----》隔行扫描-----》尺寸规模------》帧率-------》输出

 
 _______        _____________        _______        _____        ________
|       |      |             |      |       |      |     |      |        |
| input | ---> | deinterlace | ---> | scale | ---> | fps | ---> | output |
|_______|      |_____________|      |_______|      |_____|      |________|

Note that some filters change frame properties but not frame contents. E.g. the fps filter in the example above changes number of frames, but does not touch the frame contents. Another example is the setpts filter, which only sets timestamps and otherwise passes the frames unchanged.

注意:一些滤波器改变了帧的特点,但是没有改变帧的内容;例如,上图中的fps滤波器改变了帧的数目,但是没有接触帧的内容;另一个例子就是setpts滤波器,这个滤波器只是设置了时间戳,没有做其他改变;

 

complex 型的是:

 

3.1.2 Complex filtergraphs

 

Complex filtergraphs are those which cannot be described as simply a linear processing chain applied to one stream. This is the case e.g. when the graph has more than one input and/or output, or when output stream type is different from input. They can be represented with the following diagram:

复杂的过滤器图就是指:不能简单使用线性的作用于同一个数据流的处理器链表来描述;它是这样一种情况:

当图谱含有多个输入或者输出,同时输出流类型不同于输入流类型;如下图所示:

 
 _________
|         |
| input 0 |\                    __________
|_________| \                  |          |
             \   _________    /| output 0 |
              \ |         |  / |__________|
 _________     \| complex | /
|         |     |         |/
| input 1 |---->| filter  |\
|_________|     |         | \   __________
               /| graph   |  \ |          |
              / |         |   \| output 1 |
 _________   /  |_________|    |__________|
|         | /
| input 2 |/
|_________|

Complex filtergraphs are configured with the ‘-filter_complex’ option. Note that this option is global, since a complex filtergraph by its nature cannot be unambiguously associated with a single stream or file.A trivial example of a complex filtergraph is the overlay filter, which has two video inputs and one video output, containing one video overlaid on top of the other. Its audio counterpart is the amix filter.

复杂的滤波器使用'-filter_complex'参数选项来配置;注意,这个选项是全局性的,因为一个复杂的滤波器图谱在性质上不能明确的作用于一个单独的数据流或者文件?;看一个简单的例子,一个复杂的滤波器图谱可能是一个叠加的滤波器,这个滤波器有两个视频输入,但是只有一个视频输入,同时要能做到一个视频加在另一个视频的前面;它对应的音频是amix滤波器。

 

 

那好,来看一下,是怎样来配置complex类型的filtergraph的:

 

01413     for (i = 0; i < nb_filtergraphs; i++)
01414         if (!filtergraphs[i]->graph &&
01415             (ret = configure_filtergraph(filtergraphs[i])) < 0)
01416             return ret;

 

嗯,是通过调用configure_filtergraph(filtergraphs[i])来配置的,没办法,再进入configure_filtergraph()函数看一下这个函数把:

/************configure_filtergraph(filtergraphs[i])

 

00712     avfilter_graph_free(&fg->graph);释放滤波器图谱
00713     if (!(fg->graph = avfilter_graph_alloc()))为滤波器器图谱分配空间

 

 

00723     if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0)将一个由字符串graph_desc描述的滤波器图谱fg->graph添加到滤波器图谱中,注意区分添加的是输入滤波器的图谱还是输出滤波器的图谱;
00724         return ret;

 

 

00732     for (cur = inputs; !simple && init && cur; cur = cur->next)
00733         init_input_filter(fg, cur);初始化输入的滤波器

 

 

00735     for (cur = inputs, i = 0; cur; cur = cur->next, i++)
00736         if ((ret = configure_input_filter(fg, fg->inputs[i], cur)) < 0)配置输入滤波器
00737             return ret;

 

 

00740     if (!init || simple) {
00741         /* we already know the mappings between lavfi outputs and output streams,
00742          * so we can finish the setup */
00743         for (cur = outputs, i = 0; cur; cur = cur->next, i++)
00744             configure_output_filter(fg, fg->outputs[i], cur);配置输出滤波器

 

 

configure_filtergraph(filtergraphs[i])***********************/

 

下面继续回到opt_output_file()函数里来:

 

01437     err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);
为输出容器分配空间:

 

原函数是


int avformat_alloc_output_context2(AVFormatContext **ctx,即容器上下文信息,
AVOutputFormat * oformat,用来存放muxer信息,如果是NULL,则用容器的名字format_name和文件名filename代替
const char * format_name,输出格式的名字,如果是NULL,则用filename代替
const char * filename 文件名,是指存放在容器中的文件
)

进去看一下:

 

03237 int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,
03238                                    const char *format, const char *filename)
03239 {
03240     AVFormatContext *s = avformat_alloc_context();先分配一个容器格式
03241     int ret = 0;
03242 
03243     *avctx = NULL;又是二级指针的用法,此处指针指向每一个容器格式,先让它指向NULL,防止指针误用

 

 

03247     if (!oformat) {如果oformat = NULL,则分为两种情况对muxer格式进行猜测,一种是根据输出格式的名字猜测,一种是根据文件名字猜测
03248         if (format) {
03249             oformat = av_guess_format(format, NULL, NULL);
03250             if (!oformat) {
03251                 av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
03252                 ret = AVERROR(EINVAL);
03253                 goto error;
03254             }
03255         } else {
03256             oformat = av_guess_format(NULL, filename, NULL);
03257             if (!oformat) {
03258                 ret = AVERROR(EINVAL);
03259                 av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
03260                        filename);
03261                 goto error;
03262             }
03263         }
03264     }
 
 
那好,具体看一下avformat_alloc_output_context2()函数:
 
---------------------------------以下是avformat_alloc_output_context2()的分析--------------------------------------------------------------------

一、avformat_alloc_output_context2()函数:libavformat/utils.c文件中

调用顺序:

main()----->parse_options()----------->opt_output_file()-------->avformat_alloc_output_context2()

那仔细看一下传递的参数是什么:

1main()函数向parse_options()函数传递的参数:

03147     /* parse options */

03148     parse_options(&o, argc, argv, optionsopt_output_file);main()向parse_options()函数传第的参数

2、parse_options()opt_output_file()函数传递的参数是:

这个因为是通过函数指针调用的,所以直接上程序吧:

00332 void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,

00333                    void (*parse_arg_function)(void *, const char*))

00334 {

00335     const char *opt;

00336     int optindex, handleoptions = 1, ret;

00337 

00338     /* perform system-dependent conversions for arguments list */

00339     prepare_app_arguments(&argc, &argv);

00340 

00341     /* parse options */

00342     optindex = 1;解析的次数变量

00343     while (optindex < argc) {解析的参数个数当然要小于传入的参数个数

00344         opt = argv[optindex++];

00345 

00346         if (handleoptions && opt[0] == '-' && opt[1] != '\0') {

00347             if (opt[1] == '-' && opt[2] == '\0') {

00348                 handleoptions = 0;

00349                 continue;

00350             }

00351             opt++;

00352 

00353             if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)

00354                 exit_program(1);

00355             optindex += ret;

00356         } else {如果参数格式不是按照上面的格式,估计是输出文件名了

00357             if (parse_arg_function)

00358                 parse_arg_function(optctx, opt);此时,opt就是指向命令行参数中输出文件名了,比如我写输出文件名是:ffmpeg -s 1280x720 -i test.yuv -bf 0 -q 26 test.h264

那此刻,opt其实就是指向test.h264;而另一个参数optctx就是main()函数开始定义的那个OptionContext型的变量o,经过reset_options(&o,0)了,里面就是一片空白啊;

看来opt_output_file()函数就是和输出文件名死磕上了;

00359         }

00360     }

00361 }

 

3、opt_output_file()函数向avformat_alloc_output_context2()函数传递的参数是:

01437     err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);函数原型为:

 int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,

                                   const char *format, const char *filename)

传递参数分别为:

oc:容器,只是经过定义,还没有初始化的结构体指针;

oformat:输出格式,其实是NULL

o->format:格式,但是根据前面说的o,这个o->format其实只能是NULL

filename:其实就是最后一个opt,即最后一个参数选项,就是文件名了,刚才举例为test.h264

 

4、avformat_alloc_output_context2()函数向av_guess_format()函数传递的参数为:

av_guess_format(format, NULLNULL)

av_guess_format(NULL, filename, NULL);

而这个函数中format其实是对应于前面的o->format,其实就是指向NULL

所以从这个两个函数可以看出,前一个函数av_guess_format其实都是传递NULL参数,

而后一个函数av_guess_format其实传递的内容是:test.h264

 

5avformat_alloc_output_context2()函数的主要功能就是:

为输出的格式分配一个AVFormatContext

函数原型为:

 

int avformat_alloc_output_context2

(

AVFormatContext ** 

ctx,

&oc

   

AVOutputFormat * 

oformat,

NULL

   

const char * 

format_name,

o->format

   

const char * 

filename

 

filename

 

)

实际传入参数

参数介绍:

*ctx:设置用来产生AVFormatContext

oformat:用来分配AVFormatContext时提供必要的格式信息;若是NULL,则使用format_namefilename代替;

format_name:输出格式的名字,用来分配AVFormatContext时提供必要的格式信息;若为NULL,则由filename代替

filename:文件的名字,也是用来给AVFormatContext提供信息的;可能是NULL

 

6、看程序:

03237 int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,

03238                                    const char *format, const char *filename)

03239 {

03240     AVFormatContext *s = avformat_alloc_context();

调用avformat_alloc_context()函数先分配一个AVFormatContext

那进入avformat_alloc_context()看一下:

00106 AVFormatContext *avformat_alloc_context(void)

00107 {

00108     AVFormatContext *ic;

00109     ic = av_malloc(sizeof(AVFormatContext));先定义一个AVFormatContext并分配空间

00110     if (!ic) return ic;

00111     avformat_get_context_defaults(ic);调用avformat_get_context_defaults()函数获得缺省值,其实说白了就是avclass的值,其他都是0;不信向下看

00112     return ic;带着avclass的值然后返回

00113 }

下面进入avformat_get_context_defaults()函数看一下:

00097 static void avformat_get_context_defaults(AVFormatContext *s)

00098 {

00099     memset(s, 0, sizeof(AVFormatContext));先将结构体清零;

00100 

00101     s->av_class = &av_format_context_class;然后获得静态恒定的结构体首地址;具体为:static const AVClass av_format_context_class = {

    .class_name     = "AVFormatContext",类的名字

    .item_name      = format_to_name,

    .option         = options,

    .version        = LIBAVUTIL_VERSION_INT,

    .child_next     = format_child_next,

    .child_class_next = format_child_class_next,

    .category       = AV_CLASS_CATEGORY_MUXER,

    .get_category   = get_category,

};

00102 

00103     av_opt_set_defaults(s);将所有的AVOption的成员变量设置成缺省值;根据下面的可以知道av_opt_set_defaults(s)实际上什么都没干,不信你就向下看;

00104 }所以这个函数的作用就是让AVFormatContext有了avclass成员变量值

下面就进入av_opt_set_defaults(void*s)看一下:

00678 void av_opt_set_defaults(void *s)

00679 {

00680 #if FF_API_OLD_AVOPTIONS

00681     av_opt_set_defaults2(s, 0, 0);走了一圈,什么都没干,继续向下看;估计参数中的俩0都是可以修改的,所以这里可成是当你的缺省值修改时,要注意这里

00682 }

还得进入av_opt_set_defaults2(s,0,0)看一下:

 

00684 void av_opt_set_defaults2(void *s, int maskint flags)

00685 {

00686 #endif

00687     const AVOption *opt = NULL;

00688     while ((opt = av_opt_next(s, opt)) != NULL) {迭代所有属于目标文件的AVOptions,但是AVoptions已经指向NULL了,其实走到这,程序就该往回走了,因为本来opt就什么都没有,都指向NULL了,能有什么,所以从这地方返回NULL,退出av_opt_set_defaults2()函数了;

const AVOption* av_opt_next(void * obj,

const AVOption * prev 

)

Iterate over all AVOptions belonging to obj.

Parameters:

obj an AVOptions-enabled struct or a double pointer to an AVClass describing it.

prev result of the previous call to av_opt_next() on this object or NULL

00689 #if FF_API_OLD_AVOPTIONS

00690         if ((opt->flags & mask) != flags)

00691             continue;

00692 #endif

00693         switch (opt->type) {

00694             case AV_OPT_TYPE_CONST:

00695                 /* Nothing to be done here */

00696             break;

00697             case AV_OPT_TYPE_FLAGS:

00698             case AV_OPT_TYPE_INT:

00699             case AV_OPT_TYPE_INT64:

00700                 av_opt_set_int(s, opt->name, opt->default_val.i64, 0);

00701             break;

00702             case AV_OPT_TYPE_DOUBLE:

00703             case AV_OPT_TYPE_FLOAT: {

00704                 double val;

00705                 val = opt->default_val.dbl;

00706                 av_opt_set_double(s, opt->name, val, 0);

00707             }

00708             break;

00709             case AV_OPT_TYPE_RATIONAL: {

00710                 AVRational val;

00711                 val = av_d2q(opt->default_val.dbl, INT_MAX);

00712                 av_opt_set_q(s, opt->name, val, 0);

00713             }

00714             break;

00715             case AV_OPT_TYPE_STRING:

00716             case AV_OPT_TYPE_IMAGE_SIZE:

00717             case AV_OPT_TYPE_PIXEL_FMT:

00718                 av_opt_set(s, opt->name, opt->default_val.str, 0);

00719                 break;

00720             case AV_OPT_TYPE_BINARY:

00721                 /* Cannot set default for binary */

00722             break;

00723             default:

00724                 av_log(s, AV_LOG_DEBUG"AVOption type %d of option %s not implemented yet\n", opt->type, opt->name);

00725         }

00726     }

00727 }

 

 

总之根据上面分析,我们知道

avformat_alloc_context()函数就是让AVFormatContext结构体拥有了avclass成员变量值。

 

7、继续回到avformat_alloc_output_context2()函数中来:

03243     *avctx = NULL;传入的avctx参数指向NULL,估计是下面误用,先使它指向NULL

03244     if (!s)

03245         goto nomem;没有成功分配则返回错误

03246 

03247     if (!oformat) {即传入的参数中如果oformat参数指向了NULL,在这里确实指向了NULL

03248         if (format) {就是刚才说的如果oformat指向了NULL,则由format_name代替,这里format其实就是format_name参数;当然format_name参数不指向NULL才行,这里先判断一下

03249             oformat = av_guess_format(format, NULLNULL);根据formatformat_name判断oformat即输出格式;

03250             if (!oformat) {如果没有成功猜测数输出格式的话,那就错误返回吧

03251                 av_log(s, AV_LOG_ERROR"Requested output format '%s' is not a suitable output format\n", format);

03252                 ret = AVERROR(EINVAL);

03253                 goto error;

03254             }

03255         } else {如果format_name也不存在,那就只能依靠filename参数了

03256             oformat = av_guess_format(NULL, filename, NULL);根据filename判断oformat

03257             if (!oformat) {没有判断出怎错误返回

03258                 ret = AVERROR(EINVAL);

03259                 av_log(s, AV_LOG_ERROR"Unable to find a suitable output format for '%s'\n",

03260                        filename);

03261                 goto error;

03262             }

03263         }

03264     }

那我们分别看一下怎样根据format_namefilename来判断oformat的:

(1)先看根据format_name来判断的:

进入oformat = av_guess_format(format, NULL, NULL);下面是函数原型:

AVOutputFormat* av_guess_format

(

const char * 

short_name,

   

const char * 

filename,

   

const char * 

mime_type

 

 

)

     

Return the output format in the list of registered output formats which best matches the provided parameters, or return NULL if there is no match.

Parameters:

 

short_name 

if non-NULL checks if short_name matches with the names of the registered formats

 

filename 

if non-NULL checks if filename terminates with the extensions of the registered formats

 

mime_type 

if non-NULL checks if mime_type matches with the MIME type of the registered formats

 

具体看一下:

00211 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,

00212                                 const char *mime_type)

00213 {

00214     AVOutputFormat *fmt = NULL, *fmt_found;先定义一个AVOutputFormat,并将它指向NULL,防止误用

00215     int score_max, score;

00216 

00217     /* specific test for image sequences */这是对一些特定的图像序列的测试

00218 #if CONFIG_IMAGE2_MUXER

00219     if (!short_name && filename &&

00220         av_filename_number_test(filename) &&

00221         ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {

00222         return av_guess_format("image2"NULLNULL);

00223     }

00224 #endif

00225     /* Find the proper file type. */这就是找到合适文件类型了:

00226     fmt_found = NULL;

00227     score_max = 0;

00228     while ((fmt = av_oformat_next(fmt))) {如果fmtNULL,则返回第一个注册的输出格式;否则返回fmt的下一个注册的输出格式;如果fmt是最后一个,则返回NULL

根据H264来说,假设这时返回的是ff_h264_muxer或者ff_h264_demuxer

00229         score = 0;

00230         if (fmt->name && short_name && match_format(short_name, fmt->name))short_name就是传入的参数format_name了,根据这个和fmt->name匹配一下;所谓的format_name就是NULL,看1-4的分析;

00231             score += 100;

00232         if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))此时传入的参数mime_type=null

00233             score += 10;

00234         if (filename && fmt->extensions &&

00235             av_match_ext(filename, fmt->extensions)) {传入的filename也是NULL

00236             score += 5;

00237         }

00238         if (score > score_max) {如果找到对应的输出格式,则将找到的输出格式首地址赋给fmt_found

00239             score_max = score;

00240             fmt_found = fmt;

00241         }

00242     }

00243     return fmt_found;返回找到的输出格式,其实没找到;

00244 }

 

 

(2)接下来看根据filename判断format的:

00211 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,

00212                                 const char *mime_type)此时,shor_name=nullmime_type=null,只有*filename=test.h264,根据前面例子

00213 {

00214     AVOutputFormat *fmt = NULL, *fmt_found;

00215     int score_max, score;

00216 

00217     /* specific test for image sequences */

00218 #if CONFIG_IMAGE2_MUXER

00219     if (!short_name && filename &&

00220         av_filename_number_test(filename) &&

00221         ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {

00222         return av_guess_format("image2"NULLNULL);

00223     }

00224 #endif

00225     /* Find the proper file type. */

00226     fmt_found = NULL;

00227     score_max = 0;

00228     while ((fmt = av_oformat_next(fmt))) {假设返回就是h264的复用器即ff_h264_muxer

00229         score = 0;

00230         if (fmt->name && short_name && match_format(short_name, fmt->name)) short_name=NULL,这个不执行

00231             score += 100;

00232         if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))

mime_type=null,这个也不执行

00233             score += 10;

00234         if (filename && fmt->extensions &&

00235             av_match_ext(filename, fmt->extensions)) {文件名filename存在,fmt->extensions也存在,根据匹配,可知这一步可以执行

00236             score += 5;

00237         }

00238         if (score > score_max) {

00239             score_max = score;

00240             fmt_found = fmt;所以就找到ff_h264_muxer

00241         }

00242     }

00243     return fmt_found;返回的就是ff_h264_muxer

00244 }

 

 

 

8、继续回到avformat_alloc_output_context2()函数中来:

下面就是avformat_alloc_output_context2()函数所有剩下的程序:

03266     s->oformat = oformat;上面程序返回就是ff_h264_muxer或者ff_h264_demuxer了,这里就将得到的oformat赋给容器soformat成员变量,即容器获得AVOutputFormat的方式。

03267     if (s->oformat->priv_data_size > 0) {

03268         s->priv_data = av_mallocz(s->oformat->priv_data_size);

03269         if (!s->priv_data)

03270             goto nomem;

03271         if (s->oformat->priv_class) {

03272             *(const AVClass**)s->priv_data= s->oformat->priv_class;

03273             av_opt_set_defaults(s->priv_data);

03274         }

03275     } else

03276         s->priv_data = NULL;

03277 

03278     if (filename)

03279         av_strlcpy(s->filename, filename, sizeof(s->filename));

03280     *avctx = s;

03281     return 0;

03282 nomem:

03283     av_log(s, AV_LOG_ERROR"Out of memory\n");

03284     ret = AVERROR(ENOMEM);

03285 error:

03286     avformat_free_context(s);

03287     return ret;

03288 }

 

 

 
 
 
 
 
 

 

下面是:

 

03266     s->oformat = oformat;将猜测出来的muxer格式赋给容器自带的muxer
03267     if (s->oformat->priv_data_size > 0) {私有数据的处理
03268         s->priv_data = av_mallocz(s->oformat->priv_data_size);
03269         if (!s->priv_data)
03270             goto nomem;
03271         if (s->oformat->priv_class) {
03272             *(const AVClass**)s->priv_data= s->oformat->priv_class;
03273             av_opt_set_defaults(s->priv_data);根据私有数据设置缺省值
03274         }
03275     } else
03276         s->priv_data = NULL;
03277 
03278     if (filename)如果有文件名,则将文件名赋给容器自带的文件名
03279         av_strlcpy(s->filename, filename, sizeof(s->filename));
03280     *avctx = s;将传入的容器指向已经打理好的容器,即完成容器格式的初始化
03281     return 0;

 

 

---------------------------------以上是avformat_alloc_output_context2()的分析--------------------------------------------------------------------

 

 

 

好了,回到opt_output_file()函数中来:

 

 

01442     file_oformat= oc->oformat;将在上面avformat_alloc_output_context2()函数中得到的文件的muxer赋给file_oformat
01443     oc->interrupt_callback = int_cb;中断回调,要看有没有中断信号,int_cb是通过调用decode_interrupt_cb函数得到的

 

 

继续向下看:

 

01444 
01445     /* create streams for all unlabeled output pads */我估计是对一些不能用的输出进行填充,至于填充什么,我也不知道
01446     for (i = 0; i < nb_filtergraphs; i++) {
01447         FilterGraph *fg = filtergraphs[i];
01448         for (j = 0; j < fg->nb_outputs; j++) {
01449             OutputFilter *ofilter = fg->outputs[j];
01450 
01451             if (!ofilter->out_tmp || ofilter->out_tmp->name)
01452                 continue;
01453 
01454             switch (avfilter_pad_get_type(ofilter->out_tmp->filter_ctx->output_pads,
01455                                           ofilter->out_tmp->pad_idx)) {得到滤波器填充物的类型,即判断是音频、视频、字幕等
01456             case AVMEDIA_TYPE_VIDEO:    o->video_disable    = 1; break;
01457             case AVMEDIA_TYPE_AUDIO:    o->audio_disable    = 1; break;
01458             case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break;
01459             }
01460             init_output_filter(ofilter, o, oc);根据前面得到信息,初始化输出滤波器
01461         }
01462     }

 

 

01464     if (!strcmp(file_oformat->name, "ffm") &&
01465         av_strstart(filename, "http:", NULL)) {这是判断是不是网络上的文件,
01466         int j;

 

 

01467         /* special case for files sent to ffserver: we get the stream
01468            parameters from ffserver */
01469         int err = read_ffserver_streams(o, oc, filename);

 

 

01493     } else if (!o->nb_stream_maps) {如果不是网络上的多媒体文件
01494         char *subtitle_codec_name = NULL;
01495         /* pick the "best" stream of each type */挑选每种多媒体文件最适合的数据流
01496 
01497         /* video: highest resolution */视频:最高的分辨率
01498         if (!o->video_disable && oc->oformat->video_codec != AV_CODEC_ID_NONE) {
01499             int area = 0, idx = -1;
01500             int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0);测试给定的容器是否能够存放编解码器,主要是调用muxer自身的函数指针指向函数进行测试,即调用ofmt->query_codec()函数进行测试
01501             for (i = 0; i < nb_input_streams; i++) {
01502                 int new_area;
01503                 ist = input_streams[i];
01504                 new_area = ist->st->codec->width * ist->st->codec->height;
01505                 if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC))
01506                     new_area = 1;
01507                 if (ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
01508                     new_area > area) {
01509                     if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC))
01510                         continue;
01511                     area = new_area;
01512                     idx = i;
01513                 }
01514             }
01515             if (idx >= 0)
01516                 new_video_stream(o, oc, idx);
01517         }

 

 

----------------------------------------以上是opt_output_file()函数的分析----------------------------------------------------------------------------------------------------

/****---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

分享到:
评论
1 楼 zhoutao_temp 2014-11-10  
这是自己能看懂还是让别人能看得懂,您就不能把版面稍微整理一下吗?

相关推荐

    FFMPEG源码深入解析.pdf

    FFmpeg源码的深入解析可以帮助开发者和研究人员更好地理解和掌握这一多媒体处理领域的核心技术。 根据提供的文件信息,FFMPEG/FFPLAY源码剖析的书籍或文档可能包含了以下知识点: ### 第一章:概述 #### 1.1 ...

    ffmpeg-0.4.9-p20051120.rar_ffmpeg_ffmpeg源码分析

    源码分析是深入理解FFmpeg工作原理、学习编程技巧和自定义功能的重要途径。 FFmpeg的核心功能包括: 1. **编解码**:FFmpeg支持大量的音视频编码格式,如H.264、AV1、VP9、AAC、MP3等,通过libavcodec库实现。源码...

    ffmpeg源码分析, ffplay源码分析

    作者裁剪了ffplay, 只留下AVI解码播放, 详细分析了代码结构. 是学习ffmpeg不可多得的好资料. 源码包里有需要的库, 可直接使用vs编译,调试. 目前试过VS2013, 可以编译,调试. 其他版本没试过

    ffdoc (FFMPEG的最完整教程)(FFMPEG的源码剖析)

    总结起来,这个"ffdoc"教程全面覆盖了FFmpeg的各个方面,包括基本的命令行用法、核心库的使用、滤镜系统、源码分析以及实时流处理。无论是对FFmpeg感兴趣的业余爱好者还是专业的音视频开发人员,都能从中受益匪浅。...

    ffmpeg源码和相关工具文档

    FFmpeg 源码分析: 1. 文件结构:FFmpeg 源码组织有序,主要分为几个关键部分,如 libavcodec(编码库)、libavformat(格式库)、libavfilter(滤镜库)和 libavutil(通用工具库)。每个库都包含了大量的模块,...

    ffmpeg源码2.4.2版本(2014年10月版本)

    FFmpeg源码分析可以从以下几个主要方面入手: 1. **编解码库libavcodec**:这是FFmpeg的核心组件之一,包含了各种音视频编码和解码器。2.4.2版本中可能包括了H.264、AAC等主流编码格式的实现。通过阅读这些编码器和...

    FFMpeg源码

    FFmpeg的源码分析可以帮助我们深入了解音视频处理的底层机制,对于开发自定义的多媒体应用或者优化现有播放器功能具有重大意义。 FFmpeg项目包含了多个组件,如libavcodec(编码库)、libavformat(容器格式库)、...

    ffmpeg源码下载 ( ffmpeg-2.0.2.tar.gz )

    在本文中,我们将深入探讨FFmpeg的源码,以ffmpeg-2.0.2.tar.gz为例,理解其架构、核心组件及其实现的关键技术。 FFmpeg的核心组件包括: 1. **libavcodec**:这是FFmpeg的编码库,负责各种音频和视频编码格式的...

    FFMpeg_FFPlay 源码剖析(杨书良).pdf

    根据提供的文档内容,以下是关于FFmpeg和FFplay源码剖析的知识点: 1. FFMPEG/FFPLAY源码剖析涉及多个核心库的详细解析,包括libavutil、libavformat和libavcodec等。这些库构成了FFmpeg多媒体框架的基础部分,分别...

    FFMPEG源码和应用

    这些工具基于FFmpeg库实现,源码分析有助于理解多媒体处理的底层逻辑。 5. **滤镜系统**: FFmpeg的libavfilter库支持多种视觉效果和处理操作,如裁剪、缩放、旋转、颜色空间转换等。开发者可以自定义滤镜,通过...

    ffmpeg源码

    在分析FFmpeg源码时,我们可以了解到以下几个关键知识点: 1. **多媒体框架结构**:FFmpeg采用模块化的架构,主要包括libavcodec(编码/解码库)、libavformat(容器格式库)、libavfilter(滤镜库)和libavutil...

    FFMPEG源码 可以在windows下看

    1. **理解音视频处理流程**:通过分析源码,可以深入理解音视频数据的处理过程,包括解码、编码、过滤等步骤。 2. **定制化开发**:根据需求,可以修改或扩展 FFmpeg,添加新的解码器、编码器或过滤器。 3. **优化...

    ffmpeg 安装编译解码

    三、FFmpeg源码分析 文件提到了对FFmpeg源码的分析,这包括对源码中一些关键函数的定义和分析,如AV_REGISTER_ALL()、AVFORMAT_NETWORK_INIT()、AVFORMAT_OPEN_INPUT()等。这些函数是FFmpeg处理多媒体数据时的核心...

    ffmpeg源码编译脚本

    以下是关于"ffmpeg源码编译脚本"的相关知识点: 1. **FFmpeg架构**: FFmpeg由多个组件组成,包括libavcodec(编码器库)、libavformat(容器格式库)、libavfilter(过滤器库)和libavutil(通用工具库)。此外,...

    ffmpeg源码剖析.pdf

    根据提供的文档信息,本文将对《ffmpeg源码剖析》这一资料进行深入解析,重点围绕ffmpeg的组成部分、原理以及实现机制等方面展开。 ### 一、概述 #### 1.1 ffplay 文件概览 ffplay 是一个基于 ffmpeg 库构建的...

    ffmpeg.rar_ffmpeg_ffplay

    FFmpeg的源码分析可以帮助我们深入理解音视频处理的底层原理,包括编解码技术、帧处理、时序同步等核心概念。通过阅读和学习FFmpeg源码,开发者可以掌握以下关键知识点: 1. **多媒体编码和解码**:FFmpeg支持多种...

    FFMPEG FFPLAY 源码剖析_杨书良著_带目录高清.pdf

    8. **源码分析技巧**:除了技术内容,书还可能涵盖源码阅读和调试的技巧,帮助读者更高效地理解和学习FFmpeg源码。 这本书的“带目录”特点意味着内容结构清晰,方便读者查找和学习特定主题。通过深入阅读并实践书...

    FFMPEG/FFPLAY源码剖析(作者 杨书良)完整清晰pdf

    7. **FFplay源码分析**:重点分析FFplay播放器的代码,包括如何初始化、打开文件、播放控制、音视频同步等关键部分。 8. **过滤器系统**:介绍FFmpeg强大的过滤器系统,如何利用过滤器进行音视频处理,如裁剪、缩放...

Global site tag (gtag.js) - Google Analytics