以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的名字
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双精度的标志
00166 void *dst_ptr;
00167 int(*func_arg)(void *,constchar *,constchar *);
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},
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);
进入av_log_set_callback()函数,可以看到
00277voidav_log_set_callback(void (*callback)(void*,int,constchar*, va_list))
00279 av_log_callback=callback;av_log_callback是函数指针,通过指向callback,来调用callback函数
那再看一下具体的调用函数log_callback_null()函数,
是个空函数,也就是说什么都不干;
总之,这段程序就是判断一下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);
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);
00037 #define REGISTER_DECODER(X,x) { \ 00038 extern AVCodec ff_##x##_decoder; \ 00039 if(CONFIG_##X##_DECODER) avcodec_register(&ff_##x##_decoder); }
编码器的注册实际为:
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);
either this function or avcodec_register_all() must be called before any other libavcodec functions.
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()函数是怎样进行初始化的。
00130staticvoidavcodec_init(void)
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)
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, 是将-1024到1024之间的任意数转化为0~255的数,1024就是MAX_NEG_CROP的值。这样设计的实质是用查表的方式来减少所需执行的指令数量,是用空间上的代价来换取速度的提升,这是一种典型的方式。
02793 ff_squareTbl[i]= (i - 256) * (i - 256);
02796 for(i=0; i<64; i++)ff_inv_zigzag_direct16[ff_zigzag_direct[i]]=i+1;
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指针还没指向某个函数,应该是空;
00310 REGISTER_DECODER (MP3, mp3);这个先不看,以后有时间再看音频解码器的注册。
00478 REGISTER_PARSER (H264, h264);
00042 #define REGISTER_PARSER(X,x) { \ 00043 extern AVCodecParser ff_##x##_parser; \ 00044 if(CONFIG_##X##_PARSER) av_register_codec_parser(&ff_##x##_parser); }
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指向当前的解析器,也就是把当前的解析器连接到链表的尾部。
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)
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下的一种屏幕录像工具
00061 REGISTER_INDEV (LIBCDIO, libcdio);
00062 REGISTER_INDEV (LIBDC1394, libdc1394);
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);
这个是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)); }
然后调用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)
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
从上面可知这个在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 <termios.h>
#include <unistd.h>
(1)int tcgetattr(int fd,struct termios *termios_p)
用于获取文件描述符fd对应设备状态,然后置入termios_p所指向的结构体中;函数可以从后台进程中调用;但是,终端属性可能被后来的前台进程所改变
(2)int tcsetattr(int 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:如果设置IGNBRK,BRKINT就会被忽略;如果仅仅设置了BRKINT,BREAK将会丢弃输入和输出序列中的数据(flush),并且如果终端为前台进程组的控制终端,则BREAK将会产生一个SIGINT信号发送到这个前台进程组;如果IGNBRK和BRKINT都未设置,BREAK将会被当做读入了NULL字节即’\0’,除非PARMRK设置了,在这种情况下,BREAK将会被当做读入了\377\0\0序列。
PARMRK:如果未设置IGNPAR标志,在带有奇偶校验错误或者帧错误的字符前使用\377\0\0来标志;如果IGNPAR和PARMRK均未设置,则将奇偶校验错误当做\0
ISTRIP:剥离第8个bit
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 字符尺寸掩码。值为CS5,CS6,CS7,或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 <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发送多少次,就会在接收进程的 信号队列中出现多少次。
Linux在i386上的31个规则信号(regular signal)
编号 | 信号名称 | 缺省动作 | 说明 |
1 | SIGHUP | 终止 | 终止控制终端或进程 |
2 | SIGINT | 终止 | 键盘产生的中断(Ctrl-C) |
3 | SIGQUIT | dump | 键盘产生的退出 |
4 | SIGILL | dump | 非法指令 |
5 | SIGTRAP | dump | debug中断 |
6 | SIGABRT/SIGIOT | dump | 异常中止 |
7 | SIGBUS/SIGEMT | 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 | SIGSYS/SYSUNUSED | dump | 系统调用异常 |
标号为0的信号,用以测试进程是否拥有信号发送的权限,并不会被实际发送。
同一信号在不同的系统中的值可能不一样,所以最好使用信号名而不是信号值。
信号值越小,优先级越高。
(2)信号处理:
通常,进程对信号的处理方式可以是下列三种方式之一:默认方式(default,交由系统默认信号处理);忽略(ignore,不做任何处理);进程捕获信号并处理(capture)。
注意:SIGKILL和SIGSTOP不能被用户程序捕获,也不能被忽略。也就是说,SIGKILL和SIGSTOP总是会由系统默认的处理函数进行处理(最终结果是进程被终止)
(3)信号相关系统调用函数
函数原型:
Void(*signal(int signum,void(*handler)(int)))(int);
或者是:
typedef void(*sig_t)(int)
sig_t signal(int signum,sig_t handler);
函数参数:
第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP之外的任何一种信号。
第二个参数handler描述了与信号关联的动作,它可以取以下三种值:
(1)一个无返回值的函数地址:
此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为sig的信号时,就执行了handler所指定的函数。这个函数应有如下形式的定义:
Void func(int 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)
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”,返回一些具体的CPU的flags
本函数先检测出一些本地CPU架构支持的一些信息,然后根据命令行参数来确定具体的CPU标志信息,然后根据命令行参数解析出来的CPU标志,强制本地CPU使用这些标志信息:
下面看一下opt_cpuflags()函数的内容:
00566 int opt_cpuflags(void *optctx, const char *opt, const char *arg)
00567 {
00569 unsigned flags = av_get_cpu_flags();Return the flags which specify extensions supported by the CPU.返回一些标识信息,具体是指cpu支持的一些特定的扩展架构,例如ARM,PPC,X86等架构;这个函数也是只检查一次,通过checked变量来设定只检查一次。
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_*标志信息。
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;
(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 *flags, const char *s)
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;参数选项的名字
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
00269 /* TODO those are unused now */
00271 } default_val;标量选项的默认值
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, options, opt_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*))
00336 int optindex, handleoptions = 1, ret;
00338 /* perform system-dependent conversions for arguments list */执行参数列表的系统相关的转换;但是打开一看,什么都没有,原来是为以后准备用的;这是个内联函数
00339 prepare_app_arguments(&argc, &argv);
00341 /* parse options */这才是真正的解析参数选项的程序:
00343 while (optindex < argc) {这个是必须的,索引号当然要小于参数选项的个数了
00344 opt = argv[optindex++];字符型指针opt指向每一个参数选项字符串的首地址
00346 if (handleoptions && opt[0] == '-' && opt[1] != '\0') {当前解析的参数选项
00347 if (opt[1] == '-' && opt[2] == '\0') {这个是用来跳出一些误写的”--”的参数选项,继续解析后面的参数选项,在解析每一个参数选项时都要进行这样一个判断
00351 opt++;指向每一个参数选项首地址的指针后移,指向下一个待解析的参数选项
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,
这地方的parse_arg_function()函数就是void opt_output_file(void * optctx,
const char * filename
);功能是解析输出文件的格式
00358 parse_arg_function(optctx, opt);这里根据函数指针调用opt_output_file()函数,解析输出文件的一些信息;传入的参数为:OptionContext o和opt(指向每一个参数选项字符串的首地址);
先来看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)
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(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt);
00280 return AVERROR(EINVAL);
00281 }
00282 if (po->flags & HAS_ARG && !arg) {
00283 av_log(NULL, AV_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_OFFSET和OPT_SPEC型的,则dst指向该选项的地址po->u.dst_ptr
00289 dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? (uint8_t *)optctx + po->u.off
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, -INFINITY, INFINITY);
00317 } else if (po->flags & OPT_DOUBLE) {
00318 *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY);
00319 } else if (po->u.func_arg) {
00320 int ret = po->u.func_arg(optctx, opt, arg);如果正确的话,一般返回0;
00322 av_log(NULL, AV_LOG_ERROR,
00323 "Failed to set value '%s' for option '%s'\n", arg, opt);
00327 if (po->flags & OPT_EXIT)
00328 exit_program(0);
00329 return !!(po->flags & HAS_ARG);返回0或1
1)Find_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, | |||
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 |
对应此处,此
函数传入的参数为:
Context:opt
Numstr:arg
Type:OPT_*(OPT_INT64,OPT_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()函数:
由于这个函数比较大,所以只看视频处理的,其他的不看了:
先看参数吧:
Optctx:OptionContext o;
Filename:opt,具体是指每一个参数选项的首地址;
具体程序如下:
01420 void opt_output_file(void *optctx, const char *filename)
01422 OptionsContext *o = optctx;从main()的第一句就进行初始化的结构体,终于用到了
01423 AVFormatContext *oc;容器结构体终于出来了,
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.
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.
/****---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
更新时间: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 }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2013年1月18日
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------