FFMPeg代码分析:AVFormatContext结构体

2023-11-23 04:58

本文主要是介绍FFMPeg代码分析:AVFormatContext结构体,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

从先前的demo中可以看到,进入main函数所定义的第一个变量就是AVFormatContext的指针:

int main(int argc, char *argv[])
{AVFormatContext *pFormatCtx = NULL;  ....
}
而且,往下看就会知道这个结构体将贯穿函数始终,avformat_open_input、av_find_stream_info、av_read_frame、av_close_input_file都需要这个结构作为参数。该结构在FFMPEG中的作用是解封装,即根据视频文件容器的后缀名(.mp4, .avi, .flv等)分析其中编码过的音频和视频流的保存方式并将其提取以供解码器进行解码。该结构的部分定义如下:

typedef struct AVFormatContext
{    const AVClass *av_class;struct AVInputFormat *iformat;struct AVOutputFormat *oformat;    void *priv_data;AVIOContext *pb;int ctx_flags;unsigned int nb_streams;AVStream **streams;char filename[1024];int64_t start_time;int64_t duration;int bit_rate;unsigned int packet_size;int max_delay;int flags;
#define AVFMT_FLAG_GENPTS       0x0001 ///< Generate missing pts even if it requires parsing future frames.
#define AVFMT_FLAG_IGNIDX       0x0002 ///< Ignore index.
#define AVFMT_FLAG_NONBLOCK     0x0004 ///< Do not block when reading packets from input.
#define AVFMT_FLAG_IGNDTS       0x0008 ///< Ignore DTS on frames that contain both DTS & PTS
#define AVFMT_FLAG_NOFILLIN     0x0010 ///< Do not infer any values from other values, just return what is stored in the container
#define AVFMT_FLAG_NOPARSE      0x0020 ///< Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled
#define AVFMT_FLAG_NOBUFFER     0x0040 ///< Do not buffer frames when possible
#define AVFMT_FLAG_CUSTOM_IO    0x0080 ///< The caller has supplied a custom AVIOContext, don't avio_close() it.
#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 ///< Discard frames marked corrupted
#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 ///< Flush the AVIOContext every packet.
#define AVFMT_FLAG_MP4A_LATM    0x8000 ///< Enable RTP MP4A-LATM payload
#define AVFMT_FLAG_SORT_DTS    0x10000 ///< try to interleave outputted packets by dts (using this flag can slow demuxing down)
#define AVFMT_FLAG_PRIV_OPT    0x20000 ///< Enable use of private options by delaying codec open (this could be made default once all code is converted)
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Don't merge side data but keep it separate.unsigned int probesize;int max_analyze_duration;const uint8_t *key;int keylen;unsigned int nb_programs;AVProgram **programs;enum AVCodecID video_codec_id;enum AVCodecID audio_codec_id;enum AVCodecID subtitle_codec_id;unsigned int max_index_size;unsigned int max_picture_buffer;unsigned int nb_chapters;AVChapter **chapters;AVDictionary *metadata;int64_t start_time_realtime;int fps_probe_size;int error_recognition;AVIOInterruptCB interrupt_callback;int debug;
#define FF_FDEBUG_TS        0x0001int ts_id;int audio_preload;int max_chunk_duration;int max_chunk_size;int use_wallclock_as_timestamps;int avoid_negative_ts;int avio_flags;enum AVDurationEstimationMethod duration_estimation_method;unsigned int skip_initial_bytes;unsigned int correct_ts_overflow;int seek2any;int flush_packets;int probe_score;
} AVFormatContext;
该定义中部分成员变量的作用,其中与解码相关的由红色标出:
const AVClass *av_class:用于记录日志和设置AVOption的类,由avformat_alloc_context()设置。AVClass类中保存了包含该成员的类名、对象名和AVOption实例等信息;

AVInputFormat *iformat / AVOutputFormat *oformat:输入/输出格式,或为输入或为输出,这两个不能同时存在;分别用于编码和解码。AVOutputFormat类保存了文件名、扩展名、默认音视频和字幕解码器等信息;

void *priv_data:私有数据,只有iformat/oformat.priv_class非0时有效;

AVIOContext *pb:指向IO数据的缓存的指针;

int ctx_flags:媒体流信息;

unsigned int nb_streams:包含的媒体流的数量

AVStream **streams:指向实际输入/输出数据的指针

char filename[1024]:输入或输出的文件名

int64_t start_time / int64_t duration:媒体的开始时间和持续时长,一般由AVStream **streams导出;

int bit_rate:视频码率,由ffmpeg自动计算;

unsigned int packet_size:包尺寸;

int max_delay:最大延迟;

unsigned int probesize:在解码时用于探测的数据的大小,编码时不使用;

int max_analyze_duration:允许输入数据在avformat_find_stream_info()中分析的最长时间;

const uint8_t *key:关键字;

int keylen:关键字长;

unsigned int nb_programs:包含节目的数量;

AVProgram **programs:指向实际节目的指针;

enum AVCodecID video_codec_id:指定的视频解码器ID;由用户指定;

enum AVCodecID audio_codec_id:指定的音频解码器ID;由用户指定;

enum AVCodecID subtitle_codec_id:指定的字幕解码器ID;由用户指定;

unsigned int max_index_size:用于检索码流的索引值的最大字长;

unsigned int max_picture_buffer:图像缓存的最大尺寸;

unsigned int nb_chapters:章节数目;

AVChapter **chapters:指向章节数据的指针;

AVDictionary *metadata:元数据;

int64_t start_time_realtime;:码流开始的实际时间,以毫秒为单位;

int fps_probe_size:表明使用了多少帧用于格式检测;

int error_recognition:错误检测的阈值;

AVIOInterruptCB interrupt_callback:在IO层用户自定义的回调结构;

int ts_id:传输流的ID;

unsigned int skip_initial_bytes:打开媒体流时跳过的字节数;

int seek2any:是否可以移动到任意一帧的位置;

int flush_packets:每处理一个packet之后刷新io上下文;

int probe_score:格式探测的分值,上限为AVPROBE_SCORE_MAX;


指向元数据的结构体AVDictionary由两部分组成:统计计数和指向AVDictionaryEntry的指针,也就是说AVDictionary可以说是AVDictionaryEntry的封装。

struct AVDictionary 
{int count;AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry{char *key;char *value;
} AVDictionaryEntry;

了解了AVFormatContext的构造之后,可以再来看看它是如何被初始化的了。在函数avformat_open_input中,AVFormatContext的初始化由avformat_alloc_context()及其调用的函数实现:

AVFormatContext *avformat_alloc_context(void)
{AVFormatContext *ic;ic = av_malloc(sizeof(AVFormatContext));if (!ic) return ic;avformat_get_context_defaults(ic);return ic;
}
static void avformat_get_context_defaults(AVFormatContext *s)
{memset(s, 0, sizeof(AVFormatContext));s->av_class = &av_format_context_class;av_opt_set_defaults(s);
}
void av_opt_set_defaults(void *s)
{
#if FF_API_OLD_AVOPTIONSav_opt_set_defaults2(s, 0, 0);
}
void av_opt_set_defaults2(void *s, int mask, int flags)
{
#endifconst AVClass *class = *(AVClass **)s;const AVOption *opt = NULL;while ((opt = av_opt_next(s, opt)) != NULL) {
#if FF_API_OLD_AVOPTIONSif ((opt->flags & mask) != flags)continue;
#endifswitch (opt->type) {case AV_OPT_TYPE_CONST:/* Nothing to be done here */break;case AV_OPT_TYPE_FLAGS:case AV_OPT_TYPE_INT:case AV_OPT_TYPE_INT64:case AV_OPT_TYPE_DURATION:case AV_OPT_TYPE_CHANNEL_LAYOUT:av_opt_set_int(s, opt->name, opt->default_val.i64, 0);break;case AV_OPT_TYPE_DOUBLE:case AV_OPT_TYPE_FLOAT: {double val;val = opt->default_val.dbl;av_opt_set_double(s, opt->name, val, 0);}break;case AV_OPT_TYPE_RATIONAL: {AVRational val;val = av_d2q(opt->default_val.dbl, INT_MAX);av_opt_set_q(s, opt->name, val, 0);}break;case AV_OPT_TYPE_COLOR:case AV_OPT_TYPE_STRING:case AV_OPT_TYPE_IMAGE_SIZE:case AV_OPT_TYPE_VIDEO_RATE:av_opt_set(s, opt->name, opt->default_val.str, 0);break;case AV_OPT_TYPE_PIXEL_FMT:
#if LIBAVUTIL_VERSION_MAJOR < 53if (class->version && class->version < AV_VERSION_INT(52, 10, 100))av_opt_set(s, opt->name, opt->default_val.str, 0);else
#endifav_opt_set_pixel_fmt(s, opt->name, opt->default_val.i64, 0);break;case AV_OPT_TYPE_SAMPLE_FMT:
#if LIBAVUTIL_VERSION_MAJOR < 53if (class->version && class->version < AV_VERSION_INT(52, 10, 100))av_opt_set(s, opt->name, opt->default_val.str, 0);else
#endifav_opt_set_sample_fmt(s, opt->name, opt->default_val.i64, 0);break;case AV_OPT_TYPE_BINARY:/* Cannot set default for binary */break;default:av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n", opt->type, opt->name);}}
}

可以看到,在av_opt_set_defaults2()函数中,首先会调用av_opt_next(s, opt),并根据opt->type的不同值来进行下一步的设置。av_opt_next()的代码:

const AVOption *av_opt_next(void *obj, const AVOption *last)
{AVClass *class = *(AVClass**)obj;if (!last && class && class->option && class->option[0].name)return class->option;if (last && last[1].name)return ++last;return NULL;
}
由于初始化时opt=NULL,因此该函数返回AVFormatContext::AVClass::option,并在下一次调用的时候作为参数传入。在options_table.h中可以看出该函数将avformat_options[]表中的设置数据逐条地返回给调用者,并通过其type进行下一步操作。以av_opt_set_int()为例:

int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags)
{return set_number(obj, name, 1, 1, val, search_flags);
}
static int set_number(void *obj, const char *name, double num, int den, int64_t intnum, int search_flags)
{void *dst, *target_obj;const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);if (!o || !target_obj)return AVERROR_OPTION_NOT_FOUND;dst = ((uint8_t*)target_obj) + o->offset;return write_number(obj, o, dst, num, den, intnum);
}
由此我们知道了,AVFormatContext结构体中AVOption的元素通过这些函数,由options_table.h中的预定义表生成。


这篇关于FFMPeg代码分析:AVFormatContext结构体的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/415496

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)