ffmpeg小觑

Ffmpeg developers' guide

-----------------by Alan Wang

1Debug

        ffmpeg的源码上进行调试自然免不了打log,在ffmpeg中打log可以使用Ffmpeg中的API 

/**
 * Send the specified message to the log if the level is less than or equal
 * to the current av_log_level. By default, all logging messages are sent to
 * stderr. This behavior can be altered by setting a different av_vlog callback
 * function.
 *
 * @param avcl A pointer to an arbitrary struct of which the first field is a
 * pointer to an AVClass struct.
 * @param level The importance level of the message, lower values signifying
 * higher importance.
 * @param fmt The format string (printf-compatible) that specifies how
 * subsequent arguments are converted to output.
 * @see av_vlog
 */

//注意默认情况下,所有的logging msg都被输出到stderr上。
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
Ffmpeglogging system的调用关系主要如下图:












其中这三个函数的原型如下:
1void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);  //av_printf_format(3,4) ???
2void av_vlog(void *avcl, int level, const char *fmt, va_list);
//注意va_list是一个依赖编译器实现的c宏,va_list代表一个指向参数列表的指针(char *)
3void av_log_default_callback(void* ptr, int level, const char* fmt, va_list vl);

默认的情况下,av_vlog函数会调用默认的回调函数 av_log_default_callback来完成log的输出,其实这个函数内部调用的就是fprintf(stderr,....),所以默认的log都会输出到stderr,通过API
void av_log_set_callback(void (*)(void*, int, const char*, va_list));
来设置一个新的回调函数指针例如my_logging_callback,那么在这个函数的实现中可以自定义把函数输出到其他地方入stdoutmy_logging_callback的实现自然可以用fprintf

Ffmpeg强烈推荐使用av_log APIDebug,但是这样确实带来的不便性,开发者自然会想到使用printf()来打log调试。但是Ffmpeg做了一定的限制,有时候使用printf()会带来错误信息。原因是Ffmpeglibavutil/internal.h文件中将 printf ,fprintf, malloc, free,exit....等等这些函数都重新define过了事例如下:

#undef  printf
#define printf please_use_av_log_instead_of_printf
#undef  fprintf
#define fprintf please_use_av_log_instead_of_fprintf
#undef  puts
#define puts please_use_av_log_instead_of_puts

所以如果向使用printf函数,需要在使用前重新 #undef一下。

#undef printf
printf(“Msg you want to sent to stdout”);

需要补充的是如果 ffplay.c ffmpeg.c ...这些测试程序中之间用printf来打log是没问题的,但是在这些程序中打log没有太大意义。Debug ffmpeg主要目的是取debug libavformatlibavcodec...这些库,在这些库中用printf会得到下面的编译阶段错误信息:
libavformat/sbgdec.c:347: error: implicit declaration of function ‘please_use_av_log_instead_of_printf’
libavformat/sbgdec.c:348: warning: ISO C90 forbids mixed declarations and code
make: *** [libavformat/sbgdec.o] Error 1



这里补充一个知识点:

//下面是av_log的函数原型,后面的av_printf_format(3, 4)实际上是一个宏。
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);  //av_printf_format(3,4) ???

//该宏定义的文件位于 libavutil/attributes.h
av_printf_format(3,4) 这个宏展开后的结果是 :__attribute__((__format__(__printf__, fmtpos, attrpos)))
#ifdef __GNUC__
#    define av_builtin_constant_p __builtin_constant_p
#    define av_printf_format(fmtpos, attrpos) __attribute__((__format__(__printf__, fmtpos, attrpos)))
#else
#    define av_builtin_constant_p(x) 0
#    define av_printf_format(fmtpos, attrpos)
#endif 

可以看到这个__attribute__属性是一个gun c编译器的一个特色,这个attribute告诉编译器对这个av_log函数进行format检查,怎么检查?按照 printf函数的格式化方式进行参数检查。其中fmtpos(3) 代表需要格式化的字符串在av_log函数中的参数的位置是第3个,attrpos(4)表示第一个被格式化的变量值(可能是%s%d)出现在av_log函数中的参数的位置是第4个。Gnu c__attribute__作用是可以告诉编译器,让编译器做进一步的检查。

那么av_log的函数声明就可以被解释了。
Void av_log(void *avcl, int level, const char *fmt, …) __attribute__((_format_(_printf_, 3, 4)));




-----------------------------------------------------------------------------------






2,Ffmpeg上常见的数据结构及其关系,

先来看一张图:





-----------------------------------------------------------------------------------

Ffmpeg中常见的数据结构都在上面的图中了。

Ffmpeg常见数据结构介绍

1AVFormatContext (Format也有格式化的意思)

    ffmpeg中基础的数据结构,有很多attributes,其他重要的数据结构大部分要依赖它。AVFormatContext是对多媒体文件的抽象。
其中多媒体文件,包括本地的file也包括来之网络的媒体流,甚至是一个媒体流(音频流,视频流...)。之所以说AVFormatContext
是对多媒体文件的抽象,是因为其结构中对一个多媒体文件的I/O都做了封装了划分,达到了结构化的特点。
    AVFormatContext通常被API avformat_alloc_context()调用作为第一个被初始化的数据结构。
    对于输入和输出,因为共用的是同一个结构体,所以需要分别对该结构中如下定义的iformatoformat成员赋值。 
    struct AVInputFormat *iformat; 
    struct AVOutputFormat *oformat; 
    对一个AVFormatContext来说,二个成员不能同时有值,即一个AVFormatContext不能同时含有demuxermuxer
    nb_streamsstreams所表示的AVStream结构指针数组包含了所有内嵌媒体流的描述;
    iformatoformat指向对应的demuxermuxer指针; 
    pb则指向一个控制底层数据读写的ByteIOContext结构。
    packet_buffer packet_buffer_end,是两个AVPacketList的指针,分别表示已经缓存下来,但是还没有decodeAVPacket的指针
    
2.AVStream 
    
    该结构体描述一个媒体流 主要域的释义如下,其中大部分域的值可以由av_open_input_file根据文件头的信息确定,缺少的信息需要通过调用av_find_stream_info读帧及软解码进一步获取:
    index/idindex对应流的索引,这个数字是自动生成的,根据index可以从AVFormatContext.streams表中索引到该流。而id则是流的标识,依赖于具体的容器格式。比如对于MPEG TS格式,
id就是pid
    time_base:流的时间基准,是一个实数(使用两个field,分子,分母,开表示这个实数),该流中媒体数据的ptsdts都将以这个时间基准为粒度。
通常,使用av_rescale/av_rescale_q可以实现不同时间基准的转换。 
    start_time:流的起始时间,以流的时间基准为单位,通常是该流中第一个帧的pts
    duration:流的总时间,以流的时间基准为单位。 
    need_parsing:对该流parsing过程的控制域。 
    nb_frames:流内的帧数目。
    r_frame_rate/framerate/avg_frame_rate:帧率。 
    codec:指向该流对应的AVCodecContext结构,调用av_open_input_file时生成。 
    parser:指向该流对应的AVCodecParserContext结构,调用av_find_stream_info时生成。
    /**
     * Indicates that everything up to the next keyframe
     * should be discarded.
     */
    int skip_to_keyframe;//这个字段在AVStream里面可以直接跳到下一个关键帧处。

    
3.AVPacket

     Ffmpeg使用AVPacket来暂存解复用之后、解码之前的媒体数据(一a/v帧、一个subtitle包等)及附加信息(解码时间戳、显示时间戳、时长等)。
     其中: dts表示解码时间戳,pts表示显示时间戳,它们的单位是所属媒体流的时间基准(time_base)
     stream_index给出所属媒体流的索引。
     data为数据缓冲区指针,size为长度。
     duration为数据的时长,也是以所属媒体流的时间基准为单位。
     pos表示该数据在媒体流中的字节偏移量。
     destruct为用于释放数据缓冲区的函数指针;。
     flags为标志域,其中,最低为置1表示该数据是一个关键帧。 
     AVPacket结构本身只是个容器,它使用data成员引用实际的数据缓冲区。这个缓冲区通常是由av_new_packet创建的,但也可能由FfmpegAPI创建(如av_read_frame)。
 当某个AVPacket结构的数据缓冲区不再被使用时,要需要通过调用av_free_packet释放。av_free_packet调用的是结构体本身的destruct函数,它的值有两种情况:
 1)av_destruct_packet_nofree0
 2)av_destruct_packet,其中,情况1)仅仅是将datasize的值清0而已,情况2)才会真正地释放缓冲区。 Ffmpeg内部使用AVPacket结构建立缓冲区装载数据,同时提供destruct函数,
 如果Ffmpeg打算自己维护缓冲区,则将destruct设为av_destruct_packet_nofree,用户调用av_free_packet清理缓冲区时并不能够将其释放.如果FFMPEG打算自己维护缓冲区打算将
 该缓冲区彻底交给调用者,则将destruct设为av_destruct_packet,表示它能够被释放。安全起见,如果用户希望自由地使用一个Ffmpeg内部创建的AVPacket结构,最好调用av_dup_packet
 进行缓冲区的克隆,将其转化为缓冲区能够被释放的AVPacket,以免对缓冲区的不当占用造成异常错误。av_dup_packet会为destruct指针为av_destruct_packet_nofreeAVPacket新建一个缓冲区,
 然后将原缓冲区的数据拷贝至新缓冲区,置data的值为新缓冲区的地址,同时设destruct指针为av_destruct_packet
 
4.AVCodecContext

    这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,使用整个Ffmpeg库,这部分信息在调用API av_open_input_fileav_find_stream_info
的过程中根据文件的头信息及媒体流内的头部信息完成初始化。
    其中几个主要 域的释义如下: 
    extradata/extradata_size: 这个buffer中存放了解码器可能会用到的额外信息,在av_read_frame中填充。一般来说,首先,某种具体格式的demuxer在读取格式头信息的时候会填充extradata
其次,如果demuxer没有做这个事情,比如可能在头部压没有相关的编解码信息,则相应的parser会继续从已经解复用出来的媒体流中继续寻找。在没有找到任何额外信息的情况下,这个buffer指针为空。 
    time_basewidth/height:视频的宽和高。 
    sample_rate/channels:音频的采样率和信道数目。 
    sample_fmt: 音频的原始采样格式。 
    codec_name/codec_type/codec_id/codec_tag:编解码器的信息。 保存AVCodec指针和与codec相关的数据,如videowidthheightaudiosample rate等。
    AVCodecContext中的codec_typecodec_id二个变量对于 encoder/decoder的匹配来说,最为重要。 
    enum CodecType codec_type; enum CodecID codec_id; AVCodecContext中有两个成员数据结构:AVCodecAVFrame
    AVCodec记录了所要使用的Codec信息并且含有 5个函数:initencoderclosedecodeflush来完成编解码工作。AVFrame中主要是包含了编码后的帧信息,
包括本帧是否是key frame*data[4]定义的YCbCr(颜色空间相关)信息。 

5.AVInputStream/ AVOutputStream 

    根据输入和输出流的不同,前述的AVStream结构都是封装在AVInputStreamAVOutputStream结构中,在av_encode( )函数中使用。 AVInputStream中还保存的有与时间有关的信息。 
AVOutputStream中还保存有与音视频同步等相关的信息。 

6,AVPicture

AVPicture结构的数据成员很少,其结构如下:
/**
 * four components are given, that's all.
 * the last component is alpha
 */
typedef struct AVPicture {
    uint8_t *data[AV_NUM_DATA_POINTERS];
    int linesize[AV_NUM_DATA_POINTERS];     ///< number of bytes per line
} AVPicture;
因为,AVPicture的数据成员与AVFrame的前两个数据成员完全一致,所以AVFrame常常被转换成AVPicture

AVPicture中数据成员的意义其实很简单,第一个字段一般是一个数据指针数组(uint8*),数组大小一般为4,也就是说AVPicture对于一个图像的数据认定是有4种数据,且最后一个数据类型代表了图像的透明度(alpha)即data[AV_NUM_DATA_POINTERS – 1]代表了alpha数据。根据dranger大神的tutorial02可以看出来,对于YUV数据而言前三个数据类型data[0], data[1], data[2]分别代表了YUV数据的亮度(Y)两种色度(CbCr)。为什么数据会是这种布局,请自行google YUV

第二个字段代表了第一个字段中各data域的size,这样的设置也说明了一个问题,AVPicture中的数据域data是连续的,原因自行google




PS:有一部分是抄的,TL总是找我要文档输出把我烦死了,排版也懒得搞了,就当是个备份吧。读者们,sorry啦

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值