转自:http://blog.csdn.net/beitiandijun/article/details/8466432
以FFMPEG 1.0为参考,对FFMPEG源码分析,其中调用以H264为例
一、main()中;在ffmpeg.c文件中
1、OptionsContext o ={ 0 }:
初始化结构体变量o,这个结构体主要是一些参数选项;
初始化的结果是:整型和浮点型都为0,指针型成员都为NULL
疑问是,这种初始化方式到底是:
(1)初始化结构体变量的第一个成员,其他成员变量由系统采用缺省值初始化
(2)初始化所有的结构体成员
2、reset_options(&o,0):在ffmpeg_opt.c.
这是重新设置结构体体变量o,前面只是初始化,估计是以防参数选项的结构体o的某些成员变量在以往的调试过程中保留了一些参数值或者是初始化时的一些随机值,因此要将这个结构体重置,这种思想值得学习,因为我们进行反复调试的时候,可能中间强行退出,所以在退出时没有将这个参数选项的结构体释放,所以会有某些值被保留下来,会影响以后的调试或者编解码器,或者是初始化时的一些随机值恰好是参数选项的有效值,那样也会影响程序的运行结果,所以要想消除影响,只有重置这个结构体,具体如下:
下面列出一段reset_options(&o,0)的内容:
00099 void reset_options(OptionsContext *o, int is_input) 00100 { 00101 const OptionDef *po = options; 00102 OptionsContext bak= *o; 00103 int i; 00104 00105 /* all OPT_SPEC and OPT_STRING can be freed in generic way */所有这种参数选项值都可以使用这种方式释放 00106 while (po->name) { 00107 void *dst = (uint8_t*)o + po->u.off; 00108 00109 if (po->flags & OPT_SPEC) { 00110 SpecifierOpt **so = dst; 00111 int i, *count = (int*)(so + 1); 00112 for (i = 0; i < *count; i++) { 00113 av_freep(&(*so)[i].specifier); 00114 if (po->flags & OPT_STRING) 00115 av_freep(&(*so)[i].u.str); 00116 } 00117 av_freep(so); 00118 *count = 0; 00119 } else if (po->flags & OPT_OFFSET && po->flags & OPT_STRING) 00120 av_freep(dst); 00121 po++; 00122 } 00123 00124 for (i = 0; i < o->nb_stream_maps; i++) 00125 av_freep(&o->stream_maps[i].linklabel); 00126 av_freep(&o->stream_maps); 00127 av_freep(&o->audio_channel_maps); 00128 av_freep(&o->streamid_map); 00129 00130 memset(o, 0, sizeof(*o));使用memset函数重置结构体O
00131 00132 if (is_input) { 00133 o->recording_time = bak.recording_time; 00134 if (o->recording_time != INT64_MAX) 00135 av_log(NULL, AV_LOG_WARNING, 00136 "-t is not an input option, keeping it for the next output;" 00137 " consider fixing your command line.\n"); 00138 } else 00139 o->recording_time = INT64_MAX; 00140 o->mux_max_delay = 0.7; 00141 o->limit_filesize = UINT64_MAX; 00142 o->chapters_input_file = INT_MAX; 00143 00144 uninit_opts(); 00145 init_opts(); 00146 }
具体分析如下:
options是一个静态恒定的OptionDef型的数组,
00142typedefstruct{
00143 constchar *name;option的名字
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的注册:
注意:FFMPEG本身含有H264的解码器,但是不含有编码器,只是带有编码器的接口信息,所以想生成h264格式的视频格式,必须在编译FFMPEG时将libx264编译进去
(1)注册硬件加速:
00059 REGISTER_HWACCEL (H264_DXVA2, h264_dxva2); 00060 REGISTER_HWACCEL (H264_VAAPI, h264_vaapi); 00061 REGISTER_HWACCEL (H264_VDA, h264_vda);
00136 00136REGISTER_DECODER (H264, h264); 00137 REGISTER_DECODER (H264_CRYSTALHD, h264_crystalhd); 00138 REGISTER_DECODER (H264_VDA, h264_vda); 00139 REGISTER_DECODER (H264_VDPAU, h264_vdpau);
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_decoder;
if(CONFIG_H264_DECODER)
avcodec_register(&ff_h264_decoder);
04220 AVCodec ff_h264_decoder = { 04221 .name = "h264",解码器名字 04222 .type = AVMEDIA_TYPE_VIDEO,解码器解码数据流的类型:video 04223 .id = AV_CODEC_ID_H264,解码器的ID 04224 .priv_data_size = sizeof(H264Context),解码器的私有信息 04225 .init = ff_h264_decode_init,解码器的初始化,使用解码器自身函数指针指向ff_h264_decode_init()函数,以供后面解码时调用 04226 .close = h264_decode_end,解码器的关闭,使用解码器自身结构体指针指向h264_decode_end()函数 04227 .decode = decode_frame,解码器的解码步骤,这个是重点要看的,使用解码器自身结构体指针指向decode_frame()函数 04228 .capabilities = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | 04229 CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS | 04230 CODEC_CAP_FRAME_THREADS, 04231 .flush = flush_dpb, 04232 .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),解码器的长名字,更具可读性 04233 .init_thread_copy = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy), 04234 .update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context), 04235 .profiles = NULL_IF_CONFIG_SMALL(profiles), 04236 .priv_class = &h264_class,私有的类信息 04237 };
01063 av_cold int ff_h264_decode_init(AVCodecContext *avctx)
01065 H264Context *h = avctx->priv_data;将H264的解码器的上下文信息H264Context指向编解码器的上下文信息AVCodecContext的私有信息avctx->priv_data; 01066 MpegEncContext *const s = &h->s;mpeg编码器的上下文信息 01067 int i; 01068 01069 ff_MPV_decode_defaults(s);Set the given MpegEncContext to defaults for decoding. the changed fields will not depend upon the prior state of the MpegEncContext. 01070 01071 s->avctx = avctx; 01072 common_init(h); 01073 01074 s->out_format = FMT_H264;上面的H264Context看一下:这个结构体太大了,只看感觉重要的:
00261 typedef struct H264Context { 00262 MpegEncContext s;MPEG编解码器的上下文信息 00263 H264DSPContext h264dsp;存放h264dsp函数的结构体 00264 int pixel_shift; 像素转换的标志 00265 int chroma_qp[2]; // QPc色度量化 00269 int prev_mb_skipped;前一个宏块skip的标志 00270 int next_mb_skipped;下一个宏块skip的标志 00271 00272 // prediction stuff预测的事务 00273 int chroma_pred_mode;色度预测模式 00274 int intra16x16_pred_mode;帧内16x16预测模式 00275 00276 int topleft_mb_xy;上方左边宏块 00277 int top_mb_xy;上方宏块 00278 int topright_mb_xy;上方右边宏块 00279 int left_mb_xy[LEFT_MBS];左边宏块 00280 00281 int topleft_type;上方左边宏块的类型 00282 int top_type;上方宏块类型 00283 int topright_type;上方右边宏块类型 00284 int left_type[LEFT_MBS];左边类型 00290 int8_t(*intra4x4_pred_mode);帧内4x4预测模式 00291 H264PredContext hpc; 00323 int block_offset[2 * (16 * 3)];块的偏移 00329 int mb_linesize; 宏块的行尺寸 00335 SPS sps; SPS结构体 00336 00340 PPS pps; // FIXME move to Picture perhaps? (->no) do we need that?PPS结构体 下面是slice的一些信息 00347 int slice_num; 00348 uint16_t *slice_table; 00349 int slice_type; 00350 int slice_type_nos; 00351 int slice_type_fixed; 参考帧的信息 00381 unsigned int ref_count[2]; 00382 unsigned int list_count; 00383 uint8_t *list_counts; 00384 Picture ref_list[2][48]; 00387 int ref2frm[MAX_SLICES][2][64]; 00457 SPS *sps_buffers[MAX_SPS_COUNT]; 00458 PPS *pps_buffers[MAX_PPS_COUNT]; 图像信息 00488 Picture *short_ref[32]; 00489 Picture *long_ref[32]; 00490 Picture default_ref_list[2][32]; 00491 Picture *delayed_pic[MAX_DELAYED_PIC_COUNT + 2]; // FIXME size? 00492 int last_pocs[MAX_DELAYED_PIC_COUNT]; 00493 Picture *next_output_pic; 00494 int outputed_poc; 00495 int next_outputed_poc; 00504 int long_ref_count; 00505 int short_ref_count; 00507 int cabac_init_idc;cabac初始的idc 00518 int current_slice; 00534 int last_slice_type; 00548 int prev_interlaced_frame; 00581 int recovery_frame; 00586 int valid_recovery_point; 00591 // Timestamp stuff 00592 int sei_buffering_period_present; 00593 int initial_cpb_removal_delay[32]; 00595 int cur_chroma_format_idc; 00597 int16_t slice_row[MAX_SLICES]; 00599 int sync; 同步标志 00601 uint8_t parse_history[4]; 00602 int parse_history_count; 00603 int parse_last_mb; 00604 } H264Context;
01085 ff_h264_decode_init_vlc();
01097 ff_h264_reset_sei(h);
04049 static int decode_frame(AVCodecContext *avctx, void *data, 04050 int *data_size, AVPacket *avpkt) 04051 { 04052 const uint8_t *buf = avpkt->data;图像的数据信息,放进缓存 04053 int buf_size = avpkt->size;图像包的尺寸,即缓存的尺寸 04054 H264Context *h = avctx->priv_data;私有数据的指向 04055 MpegEncContext *s = &h->s; 04056 AVFrame *pict = data;帧结构 04057 int buf_index = 0; 04058 Picture *out; 04059 int i, out_idx; 04060 04061 s->flags = avctx->flags; 04062 s->flags2 = avctx->flags2;传递的参数:
04093 if(h->is_avc && buf_size >= 9 && buf[0]==1 && buf[2]==0 && (buf[4]&0xFC)==0xFC && (buf[5]&0x1F) && buf[8]==0x67){ 04094 int cnt= buf[5]&0x1f; 04095 const uint8_t *p= buf+6; 04096 while(cnt--){ 04097 int nalsize= AV_RB16(p) + 2; 04098 if(nalsize > buf_size - (p-buf) || p[2]!=0x67) 04099 goto not_extra; 04100 p += nalsize; 04101 } 04102 cnt = *(p++); 04103 if(!cnt) 04104 goto not_extra; 04105 while(cnt--){ 04106 int nalsize= AV_RB16(p) + 2; 04107 if(nalsize > buf_size - (p-buf) || p[2]!=0x68) 04108 goto not_extra; 04109 p += nalsize; 04110 } 04111 04112 return ff_h264_decode_extradata(h, buf, buf_size); 04113 }
04114 not_extra: 04115 04116 buf_index = decode_nal_units(h, buf, buf_size);解码网络层单元信息
04133 if (!(s->flags2 & CODEC_FLAG2_CHUNKS) || 04134 (s->mb_y >= s->mb_height && s->mb_height)) { 04135 if (s->flags2 & CODEC_FLAG2_CHUNKS) 04136 decode_postinit(h, 1);上面decode_postinit(h,1)函数的原型是:
int setup_finished
)[static]
Run setup operations that must be run after slice header decoding.运行设置的操作,但是必须是在slice头信息解码之后
This includes finding the next displayed frame.这里包含了下一个播放的帧。
h h264 master context
setup_finished enough NALs have been read that we can call ff_thread_finish_setup()
04140 /* Wait for second field. */ 04141 *data_size = 0; 04142 if (h->next_output_pic && (h->next_output_pic->sync || h->sync>1)) { 04143 *data_size = sizeof(AVFrame); 04144 *pict = h->next_output_pic->f; 04145 }
04152 return get_consumed_bytes(s, buf_index, buf_size);returns the number of bytes consumed for building the current frame函数原型:
static int get_consumed_bytes(MpegEncContext *s,
int buf_size
)
returns the number of bytes consumed for building the current frame
04168 static av_cold int h264_decode_end(AVCodecContext *avctx) 04169 { 04170 H264Context *h = avctx->priv_data;私有信息指针 04171 MpegEncContext *s = &h->s; 04172 04173 ff_h264_remove_all_refs(h); 04174 ff_h264_free_context(h);释放H264Context中的所有数据:例如sps和pps,还有各种量化的表格等 04175 04176 ff_MPV_common_end(s); 04177 04178 // memset(h, 0, sizeof(H264Context)); 04179 04180 return 0; 04181 }
either this function or avcodec_register_all() must be called before any other libavcodec functions.
00150 00151voidavcodec_register(AVCodec *codec) 00152 { 00153 AVCodec **p;将p定义成二级指针,估计是为了下面将编解码器连成编解码器链表的方便;p指向整个链表的首地址,使用*p存放每一个编解码器的首地址,**p就是表示编解码器的具体的字符串形式;00154 avcodec_init();编解码器的初始化,且只能初始化一次。其中负责静态查找表结构的初始化的函数:ff_dsputil_static_init(),主要是两个表结构的初始化,ff_cropTbl和ff_squareTbl的初始化;其中ff_cropTble[i]实现的功能实际上是a = i<0 ? 0 : (i>255 ? 255 : i);这个表的大小是2*MAX_NEG_CROP+256, 是将-1024到1024之间的任意数转化为0~255的数,1024就是MAX_NEG_CROP的值。这样设计的实质是用查表的方式来减少所需执行的指令数量,是用空间上的代价来换取速度的提升,这是一种典型的方式。00155 p = &first_avcodec;前面有first_avcodec的静态全局变量初始化,即static AVCodec* first_avcodec = NULL;除了第一次添加avcodec时p会指向NULL,其他时候都是指向链表的首地址,实际上first_avcode存放的就是avcodec链表的首地址;不过感觉每一次调用avcodec_register()函数都要从开始遍历编解码器链表,这样不是很浪费时间么00156 while (*p != NULL) 00157 p = &(*p)->next; 00158 *p = codec; 00159 codec->next = NULL;上边几行代码一块来看,就是将编解码器连接成链表的过程;先遍历已经存在的编解码器链表,然后将当前的编解码器插入到链表的结尾00160 00161 if (codec->init_static_data)这个函数指针主要完成编解码器静态数据的初始化00162 codec->init_static_data(codec);想进入看看这个函数,但是没进去。00163 }
来看一下avcodec_init()函数是怎样进行初始化的。
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);这个先不看,以后有时间再看音频解码器的注册。
00450 REGISTER_ENCODER (LIBX264, libx264);
00435 REGISTER_ENCODER (LIBMP3LAME, libmp3lame);
编码器的注册实际为:
extern AVCodec ff_libx264_encoder;
if(CONFIG_libx264_ENCODER)
avcodec_register(&ff_libx264_encoder);
在这里我们就看一下ff_libx264_encoder的具体内容是什么:libavcodec/libx264.c
由于ffmpeg源代码中只有H264格式视频的解码器,所以要想编码生成H264格式的视频,需要在configure时编译libx264库文件
00701 AVCodec ff_libx264_encoder = { 00702 .name = "libx264",编码器的名字是libx26400703 .type = AVMEDIA_TYPE_VIDEO,处理的多媒体类型是video 00704 .id = AV_CODEC_ID_H264,编码器的ID 00705 .priv_data_size = sizeof(X264Context),私有数据的尺寸,这是x264 codec特有的上下文信息;刚开始看的时候不明白,原来这个编解码器的priv_data就是指各种编解码器自身特有的一些特点 00706 .init = X264_init,编码器的初始化,使用编码器结构体自身函数指针指向X264_init()函数 00707 .encode2 = X264_frame,编码器的具体实现,使用编码器结构体自身函数指针指向X264_frame()函数 00708 .close = X264_close,编码器的关闭,使用编码器结构体自身函数指针指向X264_close()函数 00709 .capabilities = CODEC_CAP_DELAY | CODEC_CAP_AUTO_THREADS, 00710 .long_name = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),编码器的长名字,更具可读性 00711 .priv_class = &class,私有的类 00712 .defaults = x264_defaults,缺省值, 00713 .init_static_data = X264_init_static,初始化静态数据,使用编码器结构体自身函数指针指向X264_init_static()函数 00714 };
上面通过对编解码器的注册,调用AVCodec ff_libx264_encoder的部分初始化,借助结构体AVCodec自身的一些函数指针,指向一些调用编码器的函数,以供后面具体转码时调用
那就看一下具体的函数调用:
1)X264_init()函数:
函数原型为:
00279 static av_cold int X264_init(AVCodecContext *avctx)
也就是说ffmpeg是通过AVCodecContext结构体来向x264传递参数的
00281 X264Context *x4 = avctx->priv_data;通过avctx->priv_data将编码器参数传递给x264的编码器上下文 00282 int sw,sh; 00283 00284 x264_param_default(&x4->params);这个就是直接调用x264中的函数了,进行缺省参数的设置
下边继续看结构体X264Context:
摘录一些感觉比较重要的:
00035 typedef struct X264Context { 00036 AVClass *class;x264编码器的类信息 00037 x264_param_t params;x264编码器参数的结构体 00038 x264_t *enc;x264编码器的句柄,就好比ffmpeg中AVFormatContext一样 00039 x264_picture_t pic;x264编码器的图像的结构体 00042 AVFrame out_pic;输出的帧结构 00050 float crf;质量模式 00051 float crf_max; 00052 int cqp; 00062 int b_bias;B帧的插入概率,0~100,越大插入B帧的概率越大 00063 int b_pyramid;允许B帧做参考帧 00066 int fast_pskip;p宏块skip模式 00072 int direct_pred;直接预测模式 00073 int slice_max_size;slice的最大尺寸 00076 } X264Context;
缺省参数初始化完毕后,就是编码器的打开:
00533 x4->enc = x264_encoder_open(&x4->params);直接调用x264中x264_t *x264_encoder_open( x264_param_t * )函数,将编码器打开 00534 if (!x4->enc) 00535 return -1; 00536
然后就是编码帧的等价:
00537 avctx->coded_frame = &x4->out_pic;将二者对应起来
然后就是
00539 if (avctx->flags & CODEC_FLAG_GLOBAL_HEADER) { 00540 x264_nal_t *nal; 00541 uint8_t *p; 00542 int nnal, s, i; 00543 00544 s = x264_encoder_headers(x4->enc, &nal, &nnal);这个主要是调用x264_encoder_headers()函数,作用是返回x264整个编码过程中都会用到的SPS and PPS ;
00151 static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame, 00152 int *got_packet)传入的参数包括:
00154 X264Context *x4 = ctx->priv_data;还是先通过AVCodecContext向x264传递一些私有信息 00155 x264_nal_t *nal;网络层信息 00156 int nnal, i, ret; 00157 x264_picture_t pic_out;输出图像的结构体
00159 x264_picture_init( &x4->pic );直接调用 x264中的 x264_picture_init()函数,进行图像的初始化
00189 do { 00190 if (x264_encoder_encode(x4->enc, &nal, &nnal, frame? &x4->pic: NULL, &pic_out) < 0)直接调用libx264库中的x264_encoder_encode()函数进行编码,前面所有的东西都是为了调用这个函数做准备的。00191 return -1; 00192 00193 ret = encode_nals(ctx, pkt, nal, nnal);对网络层信息进行编码 00194 if (ret < 0) 00195 return -1; 00196 } while (!ret && !frame && x264_encoder_delayed_frames(x4->enc));
00198 pkt->pts = pic_out.i_pts; 00199 pkt->dts = pic_out.i_dts; 00200 00201 switch (pic_out.i_type) { 00202 case X264_TYPE_IDR: 00203 case X264_TYPE_I: 00204 x4->out_pic.pict_type = AV_PICTURE_TYPE_I; 00205 break; 00206 case X264_TYPE_P: 00207 x4->out_pic.pict_type = AV_PICTURE_TYPE_P; 00208 break; 00209 case X264_TYPE_B: 00210 case X264_TYPE_BREF: 00211 x4->out_pic.pict_type = AV_PICTURE_TYPE_B; 00212 break; 00213 }
00223 static av_cold int X264_close(AVCodecContext *avctx) 00224 { 00225 X264Context *x4 = avctx->priv_data; 00226 00227 av_freep(&avctx->extradata); 00228 av_free(x4->sei); 00229 00230 if (x4->enc) 00231 x264_encoder_close(x4->enc);直接调用x264_encoder_close()函数,释放编码器占有的资源
00232 00233 return 0; 00234 }
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); }
解析器的注册实际为:
extern AVCodecParser ff_h264_parser;
if(CONFIG_H264_PARSER)
avcodec_register(&ff_h264_parser);
00392 AVCodecParser ff_h264_parser = { 00393 .codec_ids = { AV_CODEC_ID_H264 },当前解码器所服务的编解码器的id,当然是AV_CODEC_ID_H264 00394 .priv_data_size = sizeof(H264Context),私有数据的尺寸,因为私有数据的指针指向H264Context 00395 .parser_init = init,解析器的初始化,使用结构体自身的函数指针指向init()函数
00396 .parser_parse = h264_parse,解析器的解析步骤,使用结构体自身的函数指针指向h264_parse()函数 00397 .parser_close = close,解析器的关闭步骤,使用结构体自身的函数指针指向close()函数 00398 .split = h264_split,解析器的划分?这个是干什么的 00399 };
00384 static int init(AVCodecParserContext *s) 00385 { 00386 H264Context *h = s->priv_data;H264Context指向私有数据 00387 h->thread_context[0] = h; 00388 h->s.slice_context_count = 1; 00389 return 0; 00390 }
00290 static int h264_parse(AVCodecParserContext *s,当前解析器的上下文信息 00291 AVCodecContext *avctx,编解码器上下文信息 00292 const uint8_t **poutbuf, int *poutbuf_size, 00293 const uint8_t *buf, int buf_size)缓冲区以及缓冲区的尺寸
00294 { 00295 H264Context *h = s->priv_data;指向当前编解码器的私有数据 00296 ParseContext *pc = &h->s.parse_context; 00297 int next;
00299 if (!h->got_first) {h->got_first是解析是否成功的标志,如果此值!=0,表示成功解析一帧 00300 h->got_first = 1; 00301 if (avctx->extradata_size) {额外的数据,有些多媒体文件需要这个 00302 h->s.avctx = avctx; 00303 // must be done like in decoder, otherwise opening the parser, 00304 // letting it create extradata and then closing and opening again 00305 // will cause has_b_frames to be always set. 00306 // Note that estimate_timings_from_pts does exactly this. 00307 if (!avctx->has_b_frames)在解码器中帧重新排序缓存的尺寸,若是等于零,则表明没有B帧存在 00308 h->s.low_delay = 1;重新排序的标志置1,标志没有重新排序的需要,或者说没有B帧 00309 ff_h264_decode_extradata(h, avctx->extradata, avctx->extradata_size);解码额外的数据 00310 } 00311 }
00313 if(s->flags & PARSER_FLAG_COMPLETE_FRAMES){如果已经完成解析,则 00314 next= buf_size; 00315 }else{否则: 00316 next= ff_h264_find_frame_end(h, buf, buf_size);根据缓存和缓存尺寸寻找帧结尾 00317 00318 if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {将截断的比特流连成一个完整的帧 00319 *poutbuf = NULL; 00320 *poutbuf_size = 0; 00321 return buf_size; 00322 }
00330 parse_nal_units(s, avctx, buf, buf_size);解析网络层
00348 return next;返回buf_size:
00375 static void close(AVCodecParserContext *s) 00376 { 00377 H264Context *h = s->priv_data; 00378 ParseContext *pc = &h->s.parse_context; 00379 00380 av_free(pc->buffer);释放缓存 00381 ff_h264_free_context(h);释放H264Context结构体 00382 } 00383
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);
在编译阶段,会提示如果不安装SDL,不会生成ffplay,不知道这个SDL是不是生成ffplay的SDL。
这个是REGISTER_OUTDEV的宏定义:
00024 #define REGISTER_OUTDEV(X,x) { \ 00025 extern AVOutputFormat ff_##x##_muxer; \ 00026 if(CONFIG_##X##_OUTDEV) av_register_output_format(&ff_##x##_muxer); }
上面的宏定义先声明AVOutputFormat类型的muxer(复用器或者说混流器)
然后调用av_register_output_foramt()函数进行注册
拿SDL来说吧:
00053 REGISTER_OUTDEV (SDL, sdl);
可以转化成:
externAVOutputFormat ff_sdl_muxer;
if(CONFIG_SDL_OUTDEV)
av_register_output_format(&ff_sdl_muxer);
av_register_output_format():utils.c文件中
将muxer保存在静态全局变量first_oformat中,这也是一个链表,当用到muxer时,通过遍历的方式在first_oformat中查找所需要muxer即可;这一点和codec的注册方式相同
00157 void av_register_output_format(AVOutputFormat *format) 00158 { 00159 AVOutputFormat **p;和上面avcodec_register()的用意一样,都是定义成二级指针,方便处理;p指向复用器muxer链表的首地址,*p指向每一个muxer的首地址,**p是指表示muxer名字 00160 p = &first_oformat;这是本程序前面的声明:static AVOutputFormat *first_oformat = NULL;注册之前先让二级指针p指向NULL型指针,当然除了第一次注册是指向NULL指针之外,后面的注册都不再指向NULL指针了,因为first_oformat不再为空,它其实指向muxer的链表的首地址。 00161 while (*p != NULL) p = &(*p)->next; 00162 *p = format; 00163 format->next = NULL;和前面一样,先遍历一遍muxer的链表,然后将当前的muxer加到链表的尾部; 00164 }
(2)输入设备的注册和输出设备的注册一致:
00055 REGISTER_INDEV (V4L2, v4l2);
宏定义具体为:
00027 #define REGISTER_INDEV(X,x) { \ 00028 extern AVInputFormat ff_##x##_demuxer; \ 00029 if(CONFIG_##X##_INDEV) av_register_input_format(&ff_##x##_demuxer); }
也是先做一个外部变量的声明,即声明某种demuxer为全局变量,方便后面程序调用这个demuxer;
然后调用av_register_input_format()函数进行具体的注册;
av_register_input_format():utils.c文件中
将demuxer保存在静态全局变量first_iformat中,这也是一个链表,当用到demuxer时,通过遍历的方式在first_iformat中查找所需要demuxer即可;这一点和muxer的注册方式相同
00148voidav_register_input_format(AVInputFormat *format) 00149 { 00150 AVInputFormat **p;和上面av_register_input_format()的用意一样,都是定义成二级指针,方便处理;p指向复用器demuxer链表的首地址,*p指向每一个demuxer的首地址,**p是指表示demuxer名字00151 p = &first_iformat;这是本程序前面的声明:static AVInputFormat *first_iformat = NULL;注册之前先让二级指针p指向NULL型指针,当然除了第一次注册是指向NULL指针之外,后面的注册都不再指向NULL指针了,因为first_iformat不再为空,它其实指向demuxer的链表的首地址。00152 while (*p != NULL) p = &(*p)->next; 00153 *p = format; 00154 format->next = NULL;和前面一样,先遍历一遍demuxer的链表,然后将当前的muxer加到链表的尾部;00155 }
(3)还有一种是同时具备输入和输出功能的设备,这种设备的注册就是将上面两种注册同时进行即可。
00051 REGISTER_INOUTDEV (OSS, oss);
宏定义为:
00030 #define REGISTER_INOUTDEV(X,x) REGISTER_OUTDEV(X,x); REGISTER_INDEV(X,x)
同时注册输入和输出设备;具体过程不再分析,和上面输入输出的注册相同。
8、avfilter_register_all():allfilters.c.
初始化滤波器系统,注册所有的内置滤波器;
滤波器的注册和上边的注册大同小异,因不是关注的重点,暂时就不写源码分析了。
(1)先调用宏定义,
(2)宏定义中调用函数avfilter_register_all()函数具体实现滤波器的注册;
但是要注意的是:一种滤波器是FFMPEG调用外部的滤波器;另一种滤波器是FFMPEG内置的滤波器。
具体注册函数为
avfilter_register():在avfilters.c文件中
函数功能是:注册滤波器;仅当你在后面打算通过avfilter_get_by_name()函数使用名字去寻找AVFilter结构时,这个函数才会用到;即使你没有注册滤波器,那后面的avfilter_open()函数依然可以为你实现滤波器。
9、av_register_all():allformats.c.
官网上给出的解释是:
Initializelibavformat and register all the muxers, demuxers and protocols.
If you do not callthis function, then you can select exactly which formats you want to support.
初始化libavformat,并且注册所有的复用器、解复用器和网络协议
如果你不想调用这个函数,你可以选择使用你想支持的格式;
这个和上面编解码器的注册一样,都可以自己编写,没必要啰啰嗦嗦一大堆东西
注册完编解码器、输入输出设备以及滤波器,终于轮到demuxer和muxer的注册了,其实。
av_register_all()函数主要通过调用下面三个函数实现具体的注册活动:
(1)av_register_input_format():utils.c文件中
(2)av_register_output_format():utils.c文件中
(3)ffurl_register_protocol():avio.c文件中;原来的av_register_protocol()函数已经不再用了
注册过程:
(1)先调用宏定义,例如H264调用为:REGISTER_MUXDEMUX(H264,h264)
(2)然后通过宏定义调用muxer和demuxer的注册函数
00002 *Register all the formats and protocols
00041voidav_register_all(void)
00042 {
00043 static int initialized;
00044
00045 if (initialized)
00046 return;
00047 initialized = 1;
00048
00049 avcodec_register_all();
这个函数也是使用initialized变量来控制本函数只能被注册一次。
在这函数中,调用了前面的avcodec_register_all()函数,但是由于前面注册过了,所以这里不会重新注册,估计是预备前面没有注册的。
具体实现过程为:
(1)先调用宏定义:
00114 REGISTER_MUXDEMUX (H264, h264);
(2)宏定义的内容为:因为H264同时为muxer和demuxer,所以将三个宏定义都列上:
00027 #define REGISTER_MUXER(X,x) { \ 00028 extern AVOutputFormat ff_##x##_muxer; \ 00029 if(CONFIG_##X##_MUXER) av_register_output_format(&ff_##x##_muxer); } 00030 00031 #define REGISTER_DEMUXER(X,x) { \ 00032 extern AVInputFormat ff_##x##_demuxer; \ 00033 if(CONFIG_##X##_DEMUXER) av_register_input_format(&ff_##x##_demuxer); } 00034 00035 #define REGISTER_MUXDEMUX(X,x) REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)
宏定义先声明muxer和demuxer的外部变量,在ffmpeg中muxer和demuxer分别抽象为:AVOutputFormat和AVInputFormat,muxer为混流器,demuxer为分流器
然后调用av_register_output_format()和av_register_input_format()分别完成muxer和demuxer的注册
muxer和demuxer的注册过程在前面avdevice_register_all()中已经讲过,这里就不再重复了。
宏定义替换后为:
extern AVOutputFormat ff_h264_muxer;
if(CONFIG_H264_MUXER)
av_register_output_format(&ff_h264_muxer);
extern AVInputFormat ff_h264_demuxer;
if(CONFIG_H264_DEMUXER)
av_register_input_foramt(&ff_h264_demuxer);
这就把H264的复用器和解复用器都注册上了
协议的注册:
以RTP的实现为例:
00297 REGISTER_PROTOCOL (RTP, rtp);
下面是宏定义的具体内容:
00037 #define REGISTER_PROTOCOL(X,x) { \ 00038 extern URLProtocol ff_##x##_protocol; \ 00039 if(CONFIG_##X##_PROTOCOL) ffurl_register_protocol(&ff_##x##_protocol, sizeof(ff_##x##_protocol)); }
先将URLProtocol型的协议声明为外部变量,URLProtocol是对协议的抽象
然后调用ffurl_register_protocol()实现具体的注册活动;
int ffurl_register_protocol(URLProtocol * protocol,int size):avio.c文件中
功能是注册URLProtocol类型的协议,其中size是URLProtocol结构体类型的尺寸,可以过滤掉协议长度太长的协议;
00096 int ffurl_register_protocol(URLProtocol *protocol, int size)协议抽象为URLProtocol类型的结构体 00097 { 00098 URLProtocol **p;定义二级指针是为了方便后面协议查找,其中p指向协议链表的首地址,*p指向每一个协议的首地址,**p指向具体协议内容 00099 if (size < sizeof(URLProtocol)) {先判断一下size尺寸是否满足protocol的需要,不能的话就从新分配空间,将协议复制到新分配的空间中00100 URLProtocol* temp = av_mallocz(sizeof(URLProtocol)); 00101 memcpy(temp, protocol, size); 00102 protocol = temp; 00103 }上面这几句可以看成是过滤掉长度大于FFMPEG所支持的协议长度的一些协议 00104 p = &first_protocol;first_protocol是在这个函数之前声明的静态全局变量: static URLProtocol *first_protocol = NULL;除了第一次注册时p为NULL指针外,其余时候均不为NULL指针,因为一旦注册开始,first_protocol其实就是整个协议注册链表的首地址。 00105 while (*p != NULL) p = &(*p)->next; 00106 *p = protocol; 00107 protocol->next = NULL;上边这几句和前面编解码器、muxer、demuxer的注册方式一致,都是先遍历整个注册链表,然后将当前要注册的协议添加到协议链表的末尾。 00108 return 0; 00109 }
10、avformat_network_init():utils.c.
官网上给出的解释是:
Do global initialization ofnetwork components.
This is optional, butrecommended, since it avoids the overhead of implicitly doing the setup foreach session.
Calling this function willbecome mandatory if using network protocols at some major version bump.
即:
对网络组件做一个全局的初始化
这是可选择的,但是建议你最好做,因为它避免了为每个会话隐式设置的开销
调用这个函数将成为强制性的,如果你使用一些更新中的主要的版本中的网络协议。
主要调用两个函数实现功能:
ff_network_init():network.c
ff_tls_init():network.c
04600 int avformat_network_init(void) 04601 { 04602 #if CONFIG_NETWORK 04603 int ret; 04604 ff_network_inited_globally = 1; 04605 if ((ret = ff_network_init()) < 0) 04606 return ret; 04607 ff_tls_init(); 04608 #endif 04609 return 0; 04610 }
进入ff_network_init()函数可知:
00126intff_network_init(void)
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。
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模式;
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时间限制被打破
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;复用器muxer的抽象结构体
01426 OutputStream *ost;输出流的结构体
01427 InputStream *ist;输入流的结构体
1)OutputStream结构体中重要的成员变量:
Int file_index:多媒体文件的索引号
Int index:数据流在输出文件中的索引号
Source_index:输入数据流的索引号
AVStream *st:输出文件数据流的抽象
AVCodec *enc:编码器的抽象
AVFrame *filtered_frame:经过滤波器的数据帧
Video only:
AVRational frame_rate:帧率
Int force_fps:强制性帧率
Int64_t *forced_kf_pts:强制关键帧的pts
Int forced_kf_count:强制性的关键帧的计数器
Int forced_kf_index:强制性的关键帧的索引号
Char* forced_keyframes:强制性的关键帧
Int fininshed:写数据完毕的标志位
Int unavailable:数据流不可用的标志位
Int Stream_copy:数据流copy的标志位
2)InputStream结构体中重要的成员变量:
Int file_index:多媒体文件的索引号
AVStream *st:输入文件中数据流的抽象
int discard:如果数据流数据将要丢弃,则此标志位为真
int decoding_needed:如果数据包必须以”raw_fifo”模式解码,则此标志位为真
AVCodec *dec:解码器
AVFrame *decoded_frame:解码帧
int64_t start:读数据开始的时间
int64_t next_dts:下一帧的dts,如果一个数据包中包含多个数据帧,则用来指当前数据包中当前帧的下一帧
int64_t dts:当前数据帧的dts
int64_t next_pts:对比上边的dts
int64_t pts:当前数据帧的pts
FrameBuffer *buffer_pool:解码数据的缓冲池
3)AVFormatContext结构体中重要的成员变量:
const AVClass *av_class:日志和AVOptions的类
struct AVInputFormat* iformat:复用器muxer,注意,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()
----------------------------------------以下是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.
那好,来看一下,是怎样来配置complex类型的filtergraph的:
01413 for (i = 0; i < nb_filtergraphs; i++) 01414 if (!filtergraphs[i]->graph && 01415 (ret = configure_filtergraph(filtergraphs[i])) < 0) 01416 return ret;
嗯,是通过调用configure_filtergraph(filtergraphs[i])来配置的,没办法,再进入configure_filtergraph()函数看一下这个函数把:
/************configure_filtergraph(filtergraphs[i])
00712 avfilter_graph_free(&fg->graph);释放滤波器图谱 00713 if (!(fg->graph = avfilter_graph_alloc()))为滤波器器图谱分配空间
00723 if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0)将一个由字符串graph_desc描述的滤波器图谱fg->graph添加到滤波器图谱中,注意区分添加的是输入滤波器的图谱还是输出滤波器的图谱; 00724 return ret;
00732 for (cur = inputs; !simple && init && cur; cur = cur->next) 00733 init_input_filter(fg, cur);初始化输入的滤波器
00735 for (cur = inputs, i = 0; cur; cur = cur->next, i++) 00736 if ((ret = configure_input_filter(fg, fg->inputs[i], cur)) < 0)配置输入滤波器 00737 return ret;
00740 if (!init || simple) { 00741 /* we already know the mappings between lavfi outputs and output streams, 00742 * so we can finish the setup */ 00743 for (cur = outputs, i = 0; cur; cur = cur->next, i++) 00744 configure_output_filter(fg, fg->outputs[i], cur);配置输出滤波器
configure_filtergraph(filtergraphs[i])***********************/
下面继续回到opt_output_file()函数里来:
01437 err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);为输出容器分配空间:
原函数是
int avformat_alloc_output_context2(AVFormatContext **ctx,即容器上下文信息,
AVOutputFormat * oformat,用来存放muxer信息,如果是NULL,则用容器的名字format_name和文件名filename代替
const char * format_name,输出格式的名字,如果是NULL,则用filename代替
const char * filename 文件名,是指存放在容器中的文件
)
进去看一下:
03237 int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, 03238 const char *format, const char *filename) 03239 { 03240 AVFormatContext *s = avformat_alloc_context();先分配一个容器格式 03241 int ret = 0; 03242 03243 *avctx = NULL;又是二级指针的用法,此处指针指向每一个容器格式,先让它指向NULL,防止指针误用
03247 if (!oformat) {如果oformat = NULL,则分为两种情况对muxer格式进行猜测,一种是根据输出格式的名字猜测,一种是根据文件名字猜测 03248 if (format) { 03249 oformat = av_guess_format(format, NULL, NULL); 03250 if (!oformat) { 03251 av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); 03252 ret = AVERROR(EINVAL); 03253 goto error; 03254 } 03255 } else { 03256 oformat = av_guess_format(NULL, filename, NULL); 03257 if (!oformat) { 03258 ret = AVERROR(EINVAL); 03259 av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", 03260 filename); 03261 goto error; 03262 } 03263 } 03264 }
一、avformat_alloc_output_context2()函数:libavformat/utils.c文件中
调用顺序:
main()----->parse_options()----------->opt_output_file()-------->avformat_alloc_output_context2()
那仔细看一下传递的参数是什么:
1、main()函数向parse_options()函数传递的参数:
03147 /* parse options */
03148 parse_options(&o, argc, argv, options, opt_output_file);main()向parse_options()函数传第的参数
2、parse_options()向opt_output_file()函数传递的参数是:
这个因为是通过函数指针调用的,所以直接上程序吧:
00332 void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
00333 void (*parse_arg_function)(void *, const char*))
00334 {
00335 const char *opt;
00336 int optindex, handleoptions = 1, ret;
00337
00338 /* perform system-dependent conversions for arguments list */
00339 prepare_app_arguments(&argc, &argv);
00340
00341 /* parse options */
00342 optindex = 1;解析的次数变量
00343 while (optindex < argc) {解析的参数个数当然要小于传入的参数个数
00344 opt = argv[optindex++];
00345
00346 if (handleoptions && opt[0] == '-' && opt[1] != '\0') {
00347 if (opt[1] == '-' && opt[2] == '\0') {
00348 handleoptions = 0;
00349 continue;
00350 }
00351 opt++;
00352
00353 if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
00354 exit_program(1);
00355 optindex += ret;
00356 } else {如果参数格式不是按照上面的格式,估计是输出文件名了
00357 if (parse_arg_function)
00358 parse_arg_function(optctx, opt);此时,opt就是指向命令行参数中输出文件名了,比如我写输出文件名是:ffmpeg -s 1280x720 -i test.yuv -bf 0 -q 26 test.h264
那此刻,opt其实就是指向test.h264;而另一个参数optctx就是main()函数开始定义的那个OptionContext型的变量o,经过reset_options(&o,0)了,里面就是一片空白啊;
看来opt_output_file()函数就是和输出文件名死磕上了;
00359 }
00360 }
00361 }
3、opt_output_file()函数向avformat_alloc_output_context2()函数传递的参数是:
01437 err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);函数原型为:
int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,
const char *format, const char *filename)
传递参数分别为:
oc:容器,只是经过定义,还没有初始化的结构体指针;
oformat:输出格式,其实是NULL
o->format:格式,但是根据前面说的o,这个o->format其实只能是NULL
filename:其实就是最后一个opt,即最后一个参数选项,就是文件名了,刚才举例为test.h264
4、avformat_alloc_output_context2()函数向av_guess_format()函数传递的参数为:
av_guess_format(format, NULL, NULL)
av_guess_format(NULL, filename, NULL);
而这个函数中format其实是对应于前面的o->format,其实就是指向NULL;
所以从这个两个函数可以看出,前一个函数av_guess_format其实都是传递NULL参数,
而后一个函数av_guess_format其实传递的内容是:test.h264
5、avformat_alloc_output_context2()函数的主要功能就是:
为输出的格式分配一个AVFormatContext;
函数原型为:
int avformat_alloc_output_context2 |
( |
ctx, |
&oc |
||
oformat, |
NULL |
||||
const char * |
format_name, |
o->format |
|||
const char * |
filename |
|
filename |
||
) |
实际传入参数 |
参数介绍:
*ctx:设置用来产生AVFormatContext
oformat:用来分配AVFormatContext时提供必要的格式信息;若是NULL,则使用format_name和filename代替;
format_name:输出格式的名字,用来分配AVFormatContext时提供必要的格式信息;若为NULL,则由filename代替
filename:文件的名字,也是用来给AVFormatContext提供信息的;可能是NULL;
6、看程序:
03237 int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,
03238 const char *format, const char *filename)
03240 AVFormatContext *s = avformat_alloc_context();
调用avformat_alloc_context()函数先分配一个AVFormatContext;
那进入avformat_alloc_context()看一下:
00106 AVFormatContext *avformat_alloc_context(void)
00107 {
00108 AVFormatContext *ic;
00109 ic = av_malloc(sizeof(AVFormatContext));先定义一个AVFormatContext并分配空间
00111 avformat_get_context_defaults(ic);调用avformat_get_context_defaults()函数获得缺省值,其实说白了就是avclass的值,其他都是0;不信向下看
00112 return ic;带着avclass的值然后返回了
下面进入avformat_get_context_defaults()函数看一下:
00097 static void avformat_get_context_defaults(AVFormatContext *s)
00098 {
00099 memset(s, 0, sizeof(AVFormatContext));先将结构体清零;
00100
00101 s->av_class = &av_format_context_class;然后获得静态恒定的结构体首地址;具体为:static const AVClass av_format_context_class = {
.class_name = "AVFormatContext",类的名字
.item_name = format_to_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
.child_next = format_child_next,
.child_class_next = format_child_class_next,
.category = AV_CLASS_CATEGORY_MUXER,
.get_category = get_category,
};
00102
00103 av_opt_set_defaults(s);将所有的AVOption的成员变量设置成缺省值;根据下面的可以知道av_opt_set_defaults(s)实际上什么都没干,不信你就向下看;
00104 }所以这个函数的作用就是让AVFormatContext有了avclass成员变量值
下面就进入av_opt_set_defaults(void*s)看一下:
00678 void av_opt_set_defaults(void *s)
00679 {
00680 #if FF_API_OLD_AVOPTIONS
00681 av_opt_set_defaults2(s, 0, 0);走了一圈,什么都没干,继续向下看;估计参数中的俩0都是可以修改的,所以这里可成是当你的缺省值修改时,要注意这里
还得进入av_opt_set_defaults2(s,0,0)看一下:
00684 void av_opt_set_defaults2(void *s, int mask, int flags)
00687 const AVOption *opt = NULL;
00688 while ((opt = av_opt_next(s, opt)) != NULL) {迭代所有属于目标文件的AVOptions,但是AVoptions已经指向NULL了,其实走到这,程序就该往回走了,因为本来opt就什么都没有,都指向NULL了,能有什么,所以从这地方返回NULL,退出av_opt_set_defaults2()函数了;
const AVOption* av_opt_next(void * obj,
const AVOption * prev
)
Iterate over all AVOptions belonging to obj.
Parameters:
obj an AVOptions-enabled struct or a double pointer to an AVClass describing it.
prev result of the previous call to av_opt_next() on this object or NULL
00689 #if FF_API_OLD_AVOPTIONS
00690 if ((opt->flags & mask) != flags)
00693 switch (opt->type) {
00694 case AV_OPT_TYPE_CONST:
00695 /* Nothing to be done here */
00697 case AV_OPT_TYPE_FLAGS:
00698 case AV_OPT_TYPE_INT:
00699 case AV_OPT_TYPE_INT64:
00700 av_opt_set_int(s, opt->name, opt->default_val.i64, 0);
00702 case AV_OPT_TYPE_DOUBLE:
00703 case AV_OPT_TYPE_FLOAT: {
00704 double val;
00705 val = opt->default_val.dbl;
00706 av_opt_set_double(s, opt->name, val, 0);
00707 }
00708 break;
00709 case AV_OPT_TYPE_RATIONAL: {
00710 AVRational val;
00711 val = av_d2q(opt->default_val.dbl, INT_MAX);
00712 av_opt_set_q(s, opt->name, val, 0);
00713 }
00714 break;
00715 case AV_OPT_TYPE_STRING:
00716 case AV_OPT_TYPE_IMAGE_SIZE:
00717 case AV_OPT_TYPE_PIXEL_FMT:
00718 av_opt_set(s, opt->name, opt->default_val.str, 0);
00720 case AV_OPT_TYPE_BINARY:
00721 /* Cannot set default for binary */
00724 av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n", opt->type, opt->name);
总之根据上面分析,我们知道
avformat_alloc_context()函数就是让AVFormatContext结构体拥有了avclass成员变量值。
7、继续回到avformat_alloc_output_context2()函数中来:
03243 *avctx = NULL;传入的avctx参数指向NULL,估计是下面误用,先使它指向NULL
03247 if (!oformat) {即传入的参数中如果oformat参数指向了NULL,在这里确实指向了NULL
03248 if (format) {就是刚才说的如果oformat指向了NULL,则由format_name代替,这里format其实就是format_name参数;当然format_name参数不指向NULL才行,这里先判断一下
03249 oformat = av_guess_format(format, NULL, NULL);根据format即format_name判断oformat即输出格式;
03250 if (!oformat) {如果没有成功猜测数输出格式的话,那就错误返回吧
03251 av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
03252 ret = AVERROR(EINVAL);
03255 } else {如果format_name也不存在,那就只能依靠filename参数了
03256 oformat = av_guess_format(NULL, filename, NULL);根据filename判断oformat
03257 if (!oformat) {没有判断出怎错误返回
03258 ret = AVERROR(EINVAL);
03259 av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
那我们分别看一下怎样根据format_name和filename来判断oformat的:
(1)先看根据format_name来判断的:
进入oformat = av_guess_format(format, NULL, NULL);下面是函数原型:
AVOutputFormat* av_guess_format |
( |
const char * |
short_name, |
|
const char * |
filename, |
|||
const char * |
mime_type |
|
||
) |
Return the output format in the list of registered output formats which best matches the provided parameters, or return NULL if there is no match.
Parameters:
short_name |
if non-NULL checks if short_name matches with the names of the registered formats |
|
filename |
if non-NULL checks if filename terminates with the extensions of the registered formats |
|
mime_type |
if non-NULL checks if mime_type matches with the MIME type of the registered formats |
具体看一下:
00211 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
00214 AVOutputFormat *fmt = NULL, *fmt_found;先定义一个AVOutputFormat,并将它指向NULL,防止误用
00217 /* specific test for image sequences */这是对一些特定的图像序列的测试
00219 if (!short_name && filename &&
00220 av_filename_number_test(filename) &&
00221 ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
00222 return av_guess_format("image2", NULL, NULL);
00223 }
00224 #endif
00225 /* Find the proper file type. */这就是找到合适文件类型了:
00226 fmt_found = NULL;
00227 score_max = 0;
00228 while ((fmt = av_oformat_next(fmt))) {如果fmt是NULL,则返回第一个注册的输出格式;否则返回fmt的下一个注册的输出格式;如果fmt是最后一个,则返回NULL;
根据H264来说,假设这时返回的是ff_h264_muxer或者ff_h264_demuxer
00229 score = 0;
00230 if (fmt->name && short_name && match_format(short_name, fmt->name))这short_name就是传入的参数format_name了,根据这个和fmt->name匹配一下;所谓的format_name就是NULL,看1-4的分析;
00231 score += 100;
00232 if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))此时传入的参数mime_type=null,
00233 score += 10;
00234 if (filename && fmt->extensions &&
00235 av_match_ext(filename, fmt->extensions)) {传入的filename也是NULL
00238 if (score > score_max) {如果找到对应的输出格式,则将找到的输出格式首地址赋给fmt_found
00243 return fmt_found;返回找到的输出格式,其实没找到;
(2)接下来看根据filename判断format的:
00211 AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
00212 const char *mime_type)此时,shor_name=null,mime_type=null,只有*filename=test.h264,根据前面例子
00213 {
00214 AVOutputFormat *fmt = NULL, *fmt_found;
00215 int score_max, score;
00216
00217 /* specific test for image sequences */
00218 #if CONFIG_IMAGE2_MUXER
00219 if (!short_name && filename &&
00220 av_filename_number_test(filename) &&
00221 ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
00222 return av_guess_format("image2", NULL, NULL);
00223 }
00224 #endif
00225 /* Find the proper file type. */
00226 fmt_found = NULL;
00227 score_max = 0;
00228 while ((fmt = av_oformat_next(fmt))) {假设返回就是h264的复用器即ff_h264_muxer
00229 score = 0;
00230 if (fmt->name && short_name && match_format(short_name, fmt->name)) short_name=NULL,这个不执行
00231 score += 100;
00232 if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
mime_type=null,这个也不执行
00233 score += 10;
00234 if (filename && fmt->extensions &&
00235 av_match_ext(filename, fmt->extensions)) {文件名filename存在,fmt->extensions也存在,根据匹配,可知这一步可以执行
00236 score += 5;
00237 }
00238 if (score > score_max) {
00239 score_max = score;
00240 fmt_found = fmt;所以就找到ff_h264_muxer了
00241 }
00242 }
00243 return fmt_found;返回的就是ff_h264_muxer了
00244 }
8、继续回到avformat_alloc_output_context2()函数中来:
下面就是avformat_alloc_output_context2()函数所有剩下的程序:
03266 s->oformat = oformat;上面程序返回就是ff_h264_muxer或者ff_h264_demuxer了,这里就将得到的oformat赋给容器s的oformat成员变量,即容器获得AVOutputFormat的方式。
03267 if (s->oformat->priv_data_size > 0) {
03268 s->priv_data = av_mallocz(s->oformat->priv_data_size);
03269 if (!s->priv_data)
03271 if (s->oformat->priv_class) {
03272 *(const AVClass**)s->priv_data= s->oformat->priv_class;
03273 av_opt_set_defaults(s->priv_data);
03279 av_strlcpy(s->filename, filename, sizeof(s->filename));
03283 av_log(s, AV_LOG_ERROR, "Out of memory\n");
03284 ret = AVERROR(ENOMEM);
03286 avformat_free_context(s);
下面是:
03266 s->oformat = oformat;将猜测出来的muxer格式赋给容器自带的muxer 03267 if (s->oformat->priv_data_size > 0) {私有数据的处理 03268 s->priv_data = av_mallocz(s->oformat->priv_data_size); 03269 if (!s->priv_data) 03270 goto nomem; 03271 if (s->oformat->priv_class) { 03272 *(const AVClass**)s->priv_data= s->oformat->priv_class; 03273 av_opt_set_defaults(s->priv_data);根据私有数据设置缺省值 03274 } 03275 } else 03276 s->priv_data = NULL; 03277 03278 if (filename)如果有文件名,则将文件名赋给容器自带的文件名 03279 av_strlcpy(s->filename, filename, sizeof(s->filename)); 03280 *avctx = s;将传入的容器指向已经打理好的容器,即完成容器格式的初始化 03281 return 0;
---------------------------------以上是avformat_alloc_output_context2()的分析--------------------------------------------------------------------
好了,回到opt_output_file()函数中来:
01442 file_oformat= oc->oformat;将在上面avformat_alloc_output_context2()函数中得到的文件的muxer赋给file_oformat 01443 oc->interrupt_callback = int_cb;中断回调,要看有没有中断信号,int_cb是通过调用decode_interrupt_cb函数得到的
继续向下看:
01444 01445 /* create streams for all unlabeled output pads */我估计是对一些不能用的输出进行填充,至于填充什么,我也不知道 01446 for (i = 0; i < nb_filtergraphs; i++) { 01447 FilterGraph *fg = filtergraphs[i]; 01448 for (j = 0; j < fg->nb_outputs; j++) { 01449 OutputFilter *ofilter = fg->outputs[j]; 01450 01451 if (!ofilter->out_tmp || ofilter->out_tmp->name) 01452 continue; 01453 01454 switch (avfilter_pad_get_type(ofilter->out_tmp->filter_ctx->output_pads, 01455 ofilter->out_tmp->pad_idx)) {得到滤波器填充物的类型,即判断是音频、视频、字幕等 01456 case AVMEDIA_TYPE_VIDEO: o->video_disable = 1; break; 01457 case AVMEDIA_TYPE_AUDIO: o->audio_disable = 1; break; 01458 case AVMEDIA_TYPE_SUBTITLE: o->subtitle_disable = 1; break; 01459 } 01460 init_output_filter(ofilter, o, oc);根据前面得到信息,初始化输出滤波器 01461 } 01462 }
01464 if (!strcmp(file_oformat->name, "ffm") && 01465 av_strstart(filename, "http:", NULL)) {这是判断是不是网络上的文件, 01466 int j;
01467 /* special case for files sent to ffserver: we get the stream 01468 parameters from ffserver */ 01469 int err = read_ffserver_streams(o, oc, filename);
01493 } else if (!o->nb_stream_maps) {如果不是网络上的多媒体文件 01494 char *subtitle_codec_name = NULL; 01495 /* pick the "best" stream of each type */挑选每种多媒体文件最适合的数据流 01496 01497 /* video: highest resolution */视频:最高的分辨率 01498 if (!o->video_disable && oc->oformat->video_codec != AV_CODEC_ID_NONE) { 01499 int area = 0, idx = -1; 01500 int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0);测试给定的容器是否能够存放编解码器,主要是调用muxer自身的函数指针指向函数进行测试,即调用ofmt->query_codec()函数进行测试 01501 for (i = 0; i < nb_input_streams; i++) { 01502 int new_area; 01503 ist = input_streams[i]; 01504 new_area = ist->st->codec->width * ist->st->codec->height; 01505 if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) 01506 new_area = 1; 01507 if (ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO && 01508 new_area > area) { 01509 if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) 01510 continue; 01511 area = new_area; 01512 idx = i; 01513 } 01514 } 01515 if (idx >= 0) 01516 new_video_stream(o, oc, idx); 01517 }
----------------------------------------以上是opt_output_file()函数的分析----------------------------------------------------------------------------------------------------
/****---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
相关推荐
FFmpeg源码的深入解析可以帮助开发者和研究人员更好地理解和掌握这一多媒体处理领域的核心技术。 根据提供的文件信息,FFMPEG/FFPLAY源码剖析的书籍或文档可能包含了以下知识点: ### 第一章:概述 #### 1.1 ...
源码分析是深入理解FFmpeg工作原理、学习编程技巧和自定义功能的重要途径。 FFmpeg的核心功能包括: 1. **编解码**:FFmpeg支持大量的音视频编码格式,如H.264、AV1、VP9、AAC、MP3等,通过libavcodec库实现。源码...
作者裁剪了ffplay, 只留下AVI解码播放, 详细分析了代码结构. 是学习ffmpeg不可多得的好资料. 源码包里有需要的库, 可直接使用vs编译,调试. 目前试过VS2013, 可以编译,调试. 其他版本没试过
总结起来,这个"ffdoc"教程全面覆盖了FFmpeg的各个方面,包括基本的命令行用法、核心库的使用、滤镜系统、源码分析以及实时流处理。无论是对FFmpeg感兴趣的业余爱好者还是专业的音视频开发人员,都能从中受益匪浅。...
FFmpeg 源码分析: 1. 文件结构:FFmpeg 源码组织有序,主要分为几个关键部分,如 libavcodec(编码库)、libavformat(格式库)、libavfilter(滤镜库)和 libavutil(通用工具库)。每个库都包含了大量的模块,...
FFmpeg源码分析可以从以下几个主要方面入手: 1. **编解码库libavcodec**:这是FFmpeg的核心组件之一,包含了各种音视频编码和解码器。2.4.2版本中可能包括了H.264、AAC等主流编码格式的实现。通过阅读这些编码器和...
FFmpeg的源码分析可以帮助我们深入了解音视频处理的底层机制,对于开发自定义的多媒体应用或者优化现有播放器功能具有重大意义。 FFmpeg项目包含了多个组件,如libavcodec(编码库)、libavformat(容器格式库)、...
在本文中,我们将深入探讨FFmpeg的源码,以ffmpeg-2.0.2.tar.gz为例,理解其架构、核心组件及其实现的关键技术。 FFmpeg的核心组件包括: 1. **libavcodec**:这是FFmpeg的编码库,负责各种音频和视频编码格式的...
根据提供的文档内容,以下是关于FFmpeg和FFplay源码剖析的知识点: 1. FFMPEG/FFPLAY源码剖析涉及多个核心库的详细解析,包括libavutil、libavformat和libavcodec等。这些库构成了FFmpeg多媒体框架的基础部分,分别...
这些工具基于FFmpeg库实现,源码分析有助于理解多媒体处理的底层逻辑。 5. **滤镜系统**: FFmpeg的libavfilter库支持多种视觉效果和处理操作,如裁剪、缩放、旋转、颜色空间转换等。开发者可以自定义滤镜,通过...
在分析FFmpeg源码时,我们可以了解到以下几个关键知识点: 1. **多媒体框架结构**:FFmpeg采用模块化的架构,主要包括libavcodec(编码/解码库)、libavformat(容器格式库)、libavfilter(滤镜库)和libavutil...
1. **理解音视频处理流程**:通过分析源码,可以深入理解音视频数据的处理过程,包括解码、编码、过滤等步骤。 2. **定制化开发**:根据需求,可以修改或扩展 FFmpeg,添加新的解码器、编码器或过滤器。 3. **优化...
三、FFmpeg源码分析 文件提到了对FFmpeg源码的分析,这包括对源码中一些关键函数的定义和分析,如AV_REGISTER_ALL()、AVFORMAT_NETWORK_INIT()、AVFORMAT_OPEN_INPUT()等。这些函数是FFmpeg处理多媒体数据时的核心...
以下是关于"ffmpeg源码编译脚本"的相关知识点: 1. **FFmpeg架构**: FFmpeg由多个组件组成,包括libavcodec(编码器库)、libavformat(容器格式库)、libavfilter(过滤器库)和libavutil(通用工具库)。此外,...
根据提供的文档信息,本文将对《ffmpeg源码剖析》这一资料进行深入解析,重点围绕ffmpeg的组成部分、原理以及实现机制等方面展开。 ### 一、概述 #### 1.1 ffplay 文件概览 ffplay 是一个基于 ffmpeg 库构建的...
FFmpeg的源码分析可以帮助我们深入理解音视频处理的底层原理,包括编解码技术、帧处理、时序同步等核心概念。通过阅读和学习FFmpeg源码,开发者可以掌握以下关键知识点: 1. **多媒体编码和解码**:FFmpeg支持多种...
8. **源码分析技巧**:除了技术内容,书还可能涵盖源码阅读和调试的技巧,帮助读者更高效地理解和学习FFmpeg源码。 这本书的“带目录”特点意味着内容结构清晰,方便读者查找和学习特定主题。通过深入阅读并实践书...
7. **FFplay源码分析**:重点分析FFplay播放器的代码,包括如何初始化、打开文件、播放控制、音视频同步等关键部分。 8. **过滤器系统**:介绍FFmpeg强大的过滤器系统,如何利用过滤器进行音视频处理,如裁剪、缩放...