FFMPEG源码分析(1)----初版--持续更新

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

以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_toff;

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.

03123     av_log_set_flags(AV_LOG_SKIP_REPEATED);

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的注册:

(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  00136	REGISTER_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_encoder;

if(CONFIG_H264_ENCODER)

         avcodec_register(&ff_h264_encoder);

解码器的注册实际为:

extern AVCodec  ff_h264_decoder;

if(CONFIG_H264_DECODER)

         avcodec_register(&ff_h264_decoder);

解析器的注册实际为:

extern AVCodecParser  ff_h264_parser;

if(CONFIG_H264_PARSER)

         avcodec_register(&ff_h264_parser);





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 
00151 void avcodec_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链表中去



(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()函数进行注册此种编解码器的解析器;


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的注册方式相同


00148 void av_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 }






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


更新时间:2012年12月19日


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









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。




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


更新时间:2012年12月22日


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










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

#include

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模式;









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


更新时间:2012年12月25日


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









isatty()函数

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

 

#include

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时间限制被打破








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


更新时间:2012年12月26日


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









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;解复用器demuxer的抽象结构体

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,注意,muxer和demuxer同时只能存在一个

struct AVOutputFormat* oformat:解复用器demuxer

void* priv_data:容器的私有数据

AVIOContext* pb:I/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()

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滤波器。




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


更新时间:2013年1月4日


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


那好,来看一下,是怎样来配置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     }

下面是:

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;

好了,回到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         }

你可能感兴趣的:(FFMPEG源码分析(1)----初版--持续更新)