FFmpeg 学习(七):FFmpeg 结构体 AVFormatContext 分析

在 FFmpeg 学习(六):FFmpeg 核心模块 libavformat 与 libavcodec 分析 中,我们分析了FFmpeg中最重要的两个模块以及重要的结构体之间的关系,本文针对结构体AVFormatContext做一下分析。

一、源码整理

首先我们先看一下结构体AVFormatContext的定义的结构体源码(位于libavformat/avformat.h,本人已经将相关注释翻译成中文,方便大家理解):

/**
 * I/O格式上下文
 * 
 * sizeof(AVFormatContext)方法不能在libav*外部调用,使用avformat_alloc_context()来创建一个AVFormatContext.
 */
typedef struct AVFormatContext {
    /**
     * 一个用来记录和指向avoptions的类。由avformat_all_context()设置。
     * 如果(de)muxer存在私有option也会输出。
     */
    const AVClass *av_class;

    /**
     * 输入容器的格式结构体
     *
     * 只在解码中生成,由avformat_open_input()生成
     */
    struct AVInputFormat *iformat;

    /**
     * 输出容器的格式的结构体
     *
     * 只在编码中生成后,必须在调用avformat_write_header()方法之前被生成好。
     */
    struct AVOutputFormat *oformat;

    /**
     * 私有数据的格式。这是一个AVOptions-enabled的结构体。
     * 当且仅当iformat/oformat.priv_class不为空的时候才会用到。
     *
     * - 编码时: 由avformat_write_header()设置
     * - 解码时: 由avformat_open_input()设置
     */
    void *priv_data;

    /**
     * 输入/输出上下文.
     *
     * - 解码时: 可以由用户自己设置(在avformat_open_intput()之前,而且必须手动关闭),也可以由avformat_open_input()设置.
     * - 编码时: 由用户设置(在avformat_write_header之前).调用者必须注意关闭和释放的问题。
     *
     * 如果在iformat/oformat.flags里面设置了AVFMT_NOFILE的标志,就不要设置设个字段。 因为在这个情况下,编解码器将以其他的方式进行I/O操作,这个字段将为NULL.
     */
    AVIOContext *pb;

    /***************************** 流信息相关字段 ***********************************/
    /**
     * 流属性标志.是AVFMTCTX_*的集合
     * 由libavformat设置.
     */
    int ctx_flags;

    /**
     * AVFormatContext.streams -- 流的数量
     *
     * 由avformat_new_stream()设置,而且不能被其他代码更改.
     */
    unsigned int nb_streams;
    /**
     * 文件中所有流的列表.新的流主要由avformat_new_stream()创建.
     *
     * - 解码时: 流是在avformat_open_input()方法里,由libavformat创建的。如果在ctx_flags里面设置了AVFMTCTX_NOHEADER,那么新的流也可能由av_read_frame()创建.
     * - 编码时: 流是由用户创建的(在调用avformat_write_header()之前).
     *
     * 在avformat_free_context()释放.
     */
    AVStream **streams;

#if FF_API_FORMAT_FILENAME
    /**
     * 输入或输出的文件名
     *
     * - 解码时: 由avformat_open_input()设置
     * - 编码时: 应该在调用avformat_write_header之前由调用者设置
     *
     * @deprecated 本字段目前已经启用,更改为使用url地址
     */
    attribute_deprecated
    char filename[1024];
#endif

    /**
     * 输入或输出的URL. 和旧文件名字段不同的是,这个字段没有长度限制.
     *
     * - 解码时: 有avformat_open_input()设置, 如果在avformat_open_input()设置的参数为NULL,则初始化为空字符串
     * - 编码时: 应该在调用avformat_writer_header()之前由调用者设置(或者调用avformat_init_output_()进行设置),如果在avformat_open_output()设置的参数为NULL,则初始化为空字符串。
*
* 调用avformat_free_context()后由libavformat释放.
*/ char *url; /** * 第一帧的时间(AV_TIME_BASE:单位为微秒),不要直接设置这个值,这个值是由AVStream推算出来的。 * * 仅用于解码,由libavformat设置. */ int64_t start_time; /** * 流的时长(单位AV_TIME_BASE:微秒) * * 仅用于解码时,由libavformat设置. */ int64_t duration; /** * 所有流的比特率,如果不可用的时候为0。不要设置这个字段,这个字段的值是由FFmpeg自动计算出来的。 */ int64_t bit_rate; unsigned int packet_size; int max_delay; /** * 用于修改编(解)码器行为的标志,由AVFMT_FLAG_*集合构成,需要用户在调用avformat_open_input()或avformat_write_header()之前进行设置 */ int flags; #define AVFMT_FLAG_* 0x**** //***** /** * 在确定输入格式的之前的最大输入数据量. * 仅用于解码, 在调用avformat_open_input()之前设置。 */ int64_t probesize; /** * 从avformat_find_stream_info()的输入数据里面读取的最大时长(单位AV_TIME_BASE:微秒) * 仅用于解码, 在avformat_find_stream_info()设置 * 可以设置0让avformat使用启发式机制. */ int64_t max_analyze_duration; const uint8_t *key; int keylen; unsigned int nb_programs; AVProgram **programs; /** * 强制使用指定codec_id视频解码器 * 仅用于解码时: 由用户自己设置 */ enum AVCodecID video_codec_id; /** * 强制使用指定codec_id音频解码器 * 仅用于解码时: 由用户自己设置. */ enum AVCodecID audio_codec_id; /** * 强制使用指定codec_id字母解码器 * 仅用于解码时: 由用户自己设置. */ enum AVCodecID subtitle_codec_id; /** * 每个流的最大内存索引使用量。 * 如果超过了大小,就会丢弃一些,这可能会使得seek操作更慢且不精准。 * 如果提供了全部内存使用索引,这个字段会被忽略掉. * - 编码时: 未使用 * - 解码时: 由用户设置 */ unsigned int max_index_size; /** * 最大缓冲帧的内存使用量(从实时捕获设备中获得的帧数据) */ unsigned int max_picture_buffer; /** * AVChapter数组的数量 */ unsigned int nb_chapters; AVChapter **chapters; /** * 整个文件的元数据 * * - 解码时: 在avformat_open_input()方法里由libavformat设置 * - 编码时: 可以由用户设置(在avformat_write_header()之前) * * 在avformat_free_context()方法里面由libavformat释放 */ AVDictionary *metadata; /** * 流开始的绝对时间(真实世界时间) */ int64_t start_time_realtime; /** * 用于确定帧速率的帧数 * 仅在解码时使用 */ int fps_probe_size; /** * 错误识别级别. */ int error_recognition; /** * I/O层的自定义中断回调. */ AVIOInterruptCB interrupt_callback; /** * 启动调试的标志 */ int debug; #define FF_FDEBUG_TS 0x0001 /** * 最大缓冲持续时间 */ int64_t max_interleave_delta; /** * 允许非标准扩展和实验 */ int strict_std_compliance; /** * 检测文件上发生事件的标志 */ int event_flags; #define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 /** * 等待第一个事件戳要读取的最大包数 * 仅解码 */ int max_ts_probe; /** * 在编码期间避免负时间戳. * 值的大小应该是AVFMT_AVOID_NEG_TS_*其中之一. * 注意,这个设置只会在av_interleaved_write_frame生效 * - 编码时: 由用户设置 * - 解码时: 未使用 */ int avoid_negative_ts; #define AVFMT_AVOID_NEG_TS_* /** * 传输流id. * 这个将被转移到解码器的私有属性. 所以没有API/ABI兼容性 */ int ts_id; /** * 音频预加载时间(单位:毫秒) * 注意:并非所有的格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. * - 编码时: 由用户设置 * - 解码时: 未使用 */ int audio_preload; /** * 最大块时间(单位:微秒). * 注意:并非所有格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. * - 编码时: 由用户设置 * - 解码时: 未使用 */ int max_chunk_duration; /** * 最大块大小(单位:bytes) * 注意:并非所有格式都支持这个功能,如果在不支持的时候使用,可能会发生不可预测的事情. * - 编码时: 由用户设置 * - 解码时: 未使用 */ int max_chunk_size; /** * 强制使用wallclock时间戳作为数据包的pts/dts */ int use_wallclock_as_timestamps; /** * avio标志 */ int avio_flags; /** * 可以用各种方法估计事件的字段 */ enum AVDurationEstimationMethod duration_estimation_method; /** * 打开流时跳过初始字节 */ int64_t skip_initial_bytes; /** * 纠正单个时间戳溢出 */ unsigned int correct_ts_overflow; /** * 强制寻找任何帧 */ int seek2any; /** * 在每个包只会刷新I/O context */ int flush_packets; /** * 格式探索得分 */ int probe_score; /** * 最大读取字节数(用于识别格式) */ int format_probesize; /** * 允许的编码器列表(通过','分割) */ char *codec_whitelist; /** * 允许的解码器列表(通过','分割 ) */ char *format_whitelist; ......./** * 强制视频解码器 */ AVCodec *video_codec; /** * 强制音频解码器 */ AVCodec *audio_codec; /** * 强制字母解码器 */ AVCodec *subtitle_codec; /** * 强制数据解码器 */ AVCodec *data_codec; /** * 在元数据头中写入填充的字节数 */ int metadata_header_padding; /** * 用户数据(放置私人数据的地方) */ void *opaque; /** * 用于设备和应用程序之间的回调 */ av_format_control_message control_message_cb; /** * 输出时间戳偏移量(单位:微秒) */ int64_t output_ts_offset; /** * 转储格式分隔符 */ uint8_t *dump_separator; /** * 强制使用的数据解码器id */ enum AVCodecID data_codec_id; #if FF_API_OLD_OPEN_CALLBACKS /** * 需要为解码开启更多的IO contexts时调用 * @deprecated 已弃用,建议使用io_open and io_close. */ attribute_deprecated int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options); #endif /** * ',' separated list of allowed protocols. * - encoding: unused * - decoding: set by user */ char *protocol_whitelist; /** * 打开新IO流的回调 */ int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options); /** * 关闭流的回调(流是由AVFormatContext.io_open()打开的) */ void (*io_close)(struct AVFormatContext *s, AVIOContext *pb); /** * ',' 单独的不允许的协议的列表 * - 编码: 没使用到 * - 解码: 由用户设置 */ char *protocol_blacklist; /** * 最大流数 * - 编码: 没使用到 * - 解码: 由用户设置 */ int max_streams; } AVFormatContext;

二、AVForamtContext 重点字段

在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):

struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据

视频的时长可以转换成HH:MM:SS的形式,示例代码如下:

AVFormatContext *pFormatCtx;
CString timelong;
...
//duration是以微秒为单位
//转换成hh:mm:ss形式
int tns, thh, tmm, tss;
tns  = (pFormatCtx->duration)/1000000;
thh  = tns / 3600;
tmm  = (tns % 3600) / 60;
tss  = (tns % 60);
timelong.Format("%02d:%02d:%02d",thh,tmm,tss);

视频的原数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中,如下所示:

typedef struct AVDictionaryEntry {
    char *key;
    char *value;
} AVDictionaryEntry;

每一条元数据分为key和value两个属性。

在ffmpeg中通过av_dict_get()函数获得视频的原数据。

下列代码显示了获取元数据并存入meta字符串变量的过程,注意每一条key和value之间有一个"\t:",value之后有一个"\r\n"

//MetaData------------------------------------------------------------
//从AVDictionary获得
//需要用到AVDictionaryEntry对象
//CString author,copyright,description;
CString meta=NULL,key,value;
AVDictionaryEntry *m = NULL;
//不用一个一个找出来
/*    
m=av_dict_get(pFormatCtx->metadata,"author",m,0); author.Format("作者:%s",m->value); m=av_dict_get(pFormatCtx->metadata,"copyright",m,0); copyright.Format("版权:%s",m->value); m=av_dict_get(pFormatCtx->metadata,"description",m,0); description.Format("描述:%s",m->value);
*/ //使用循环读出 //(需要读取的数据,字段名称,前一条字段(循环时使用),参数) while(m=av_dict_get(pFormatCtx->metadata,"",m,AV_DICT_IGNORE_SUFFIX)){ key.Format(m->key); value.Format(m->value); meta+=key+"\t:"+value+"\r\n" ; }

猜你喜欢

转载自www.cnblogs.com/renhui/p/9361276.html
今日推荐