01FFMPEG的AVFormatContext结构体分析和输出时AVFormatContext的初始化(包含有输入和无输入的AVFormatContext)
提醒:接下来对所有源码的分析都是针对于目前最新版本的avformat5.8源码。
概述:
该结构体位于libavformat库中的avformat.h中。
前提知识:
muxing:复用或者叫封装。
demuxing:解复用或者叫解封装。
Encoding:编码。
Decoding:解码。
codec(ing):编解码。
1 AVFormatContext封装上下文(根)
该结构体的英文注释已经全部被我翻译成中文,非常方便大家理解,部分成员没用过可能不怎么理解,但是不需要管他,因为FFmpeg内部帮我们处理。
在实际编码时,我们通常需要定义两个AVFormatContext的结构体,一个用于输入,一个用于输出。
而有时候只需要一个输出的AVFormatContext封装上下文结构体(所以输出的封装上下文是必须定义的)。例如对于无法调用()直接打开海康老设备进行写帧,这就需要我们先PS流获取H264,再通过输出的AVFormatContext封装上下文,组成AVpacket一帧一帧的写过去。
所以这里可以得出:
1)对于有两个AVFormatContext的,输入的那个的结构体已经由avformat_open_input初始化,并且里面的成员iformat和streams都是有值的;
2)对于只有一个输出的AVFormatContext封装上下文,FFmpeg只是帮你开辟的内存,里面的oformat实际并不需要处理,只有streams(需要new新的流和初始化赋值编码上下文streams->codec)需要自己处理。
3)所以总结这两点:不管两个还是一个,实际上需要处理的都是输出的封装上下文,只不过区别是从输入的封装上下文拷贝还是单独自己初始化赋值。
下面底部将给出一个封装上下文(下面的例子)和两个封装上下文的例子(雷神的)。
/*
* 封装上下文结构体:存放着输入文件的视频信息和属性,是FFmepg最大的根结构体。
*/
typedef struct AVFormatContext {
/**
* 用于日志和@ref avoptions的类。由avformat_alloc_context()设置。
* 导出(de)muxer私有选项如果存在。
*/
const AVClass *av_class;
/**
* 输入容器的结构体。
* 只有demuxing解复用(可理解为输入容器支持哪些解码器), 通过avformat_open_input()函数初始化设置.
*/
ff_const59 struct AVInputFormat *iformat;
/**
* 输出容器的结构体
* 只有muxing复用(可理解为输出容器支持哪些编码器),调用者必须在调用avformat_write_header()写头之前自行初始化设置该结构体
*/
ff_const59 struct AVOutputFormat *oformat;
/**
* 格式化私有数据。这是一个支持启用AVOptions的结构
* 当且仅当iformat/oformat的成员priv_class不为空。
*
* - muxing复用: 由avformat_write_header()设置
* - demuxing解复用: 由avformat_open_input()设置
*/
void *priv_data;
/**
* I/O context.
*
* - demuxing解复用: 要么用户在调用avformat_open_input()之前设置该字段(此时必须手动关闭它),
* 要么通过调用avformat_open_input()自行设置。一般选后者,调用后不需要处理任何东西。
* - muxing复用: 必须在调用avformat_write_header()之前设置. 调用者必须小心的关闭和释放这个AVIOContext字段。
* 个人经验:一般会调用avformat_alloc_output_context2初始化大结构即封装上下文AVFormatContext,然后再调用avio_open2对该AVIOContext进行打开网络流。释放时可以直接调用avformat_close_input函数帮你释放,非常方便。
*
* 如果设置了iformat/oformat.flags = AVFMT_NOFILE,那么就不要再设置该字段。因为(de)muxer在处理I/O网络流时会认为该是空的。你再使用它,再自己的代码按照这种编码逻辑肯定会发生错误。
*
*
AVIOContext *pb;
/* stream info */
/**
* 标记信号流的属性。和AVFMTCTX_*为一个组合(有关系),通过libavformat设置。
* 不怎么常用,不需要担心。
*/
int ctx_flags;
/**
* AVFormatContext.streams数组的元素个数.一般只有两个,分别是0(视频流)和1(音频流)
*
* 由avformat_new_stream()设置,不能被任何其他代码修改,所以修改只能通过调用avformat_new_stream()增加流个数。但绝大多数不会超过2。
*/
unsigned int nb_streams;
/**
* 流数组,包含着输入文件的视频和音频信息,最重要的成员。
* 可以通过avformat_new_stream()往数组中添加元素。
*
* - demuxing解复用: 解码时由avformat_open_input()自动创建.
* 如果设置了上面的字段ctx_flags=AVFMTCTX_NOHEADER,那么创建新的流也可能会出现在av_read_frame()。
* - muxing复用: 调用者必须在调用avformat_write_header()之前创建,使用avformat_new_stream创建并对其成员编码器(解复用时叫解码器)stream->codec->codec和编码器上下文stream->codec进行相应初始化.
* 注:编码器和解码器实际上是一个东西,只是在复用和解复用时不同的叫法,不需要纠结。
*
* 可以通过avformat_free_context()释放。但我一般通过avcodec_close将数组元素中的编码器上下文释放,其余空间交给avformat_close_input帮你释放,下面将给出释放的例子。
*/
AVStream **streams;
#if FF_API_FORMAT_FILENAME
/**
* input or output filename
*
* - demuxing(解复用): 若设置了上面的宏,解码时由avformat_open_input()自行设置。
* - muxing(复用): 若设置了上面的宏,由调用者调用avformat_write_header()之前设置。
*
* 使用url代替。
* 上一句话的意思应该是:具有attribute_deprecated关键字的,笔者认为它未来将使用url代替。输入的为拉流url,输出的为推流目标的url。
*/
attribute_deprecated
char filename[1024];
#endif
/**
* 删除上面的老url即文件名字段。并且本url字段没有长度限制。
*
* - demuxing: 由avformat_open_input()设置。如果avformat_open_input()中的url参数为NULL,则初始化为空字符串。
* - muxing: 可能由调用者在调用avformat_write_header()之前设置。或者avformat_init_output()被调用,调用者调用av_free()清空字符串。那么FFMPEG发现avformat_init_output()中的值为空,则设置为空字符串。 绝大多数使用前者(没有憨憨设置为空吧),调用者自行调用avformat_alloc_output_context2设置自己的文件名。
*
* 内存由avformat_free_context()释放,不需要自己释放。
*/
char *url;
/**
* 首帧部分的位置,精确到微秒即单位AV_TIME_BASE,千万不要直接设置这个值,它是从AVStream值推导出来的。
* 只有解复用时才由libavformat内部设置。
*/
int64_t start_time;
/**
* 流的时长,精确到微秒即单位AV_TIME_BASE,仅当您不知道单个流的时长和且没有设置过任何一个流的时长才设置此值。如果没有设置,它会自动从AVStream值推断的。
*
* 只有解复用时才由libavformat内部设置。
*/
int64_t duration;
/**
* 流的总比特率(比特/秒), 如果流不可用则比特率为0。
* 千万不要直接设置它如果文件大小和时长已知的话,因为FFmpeg会自动计算它。
*/
int64_t bit_rate;
unsigned int packet_size;//包大小和最大延迟
int max_delay;
/**
* 修改(de)muxer行为的标志。和AVFMT_FLAG_*是一个组合(与其相关)。
* 由调用者设置在调用avformat_open_input() / avformat_write_header()之前.
*/
int flags;
#define AVFMT_FLAG_GENPTS 0x0001 ///< 生成丢失的pts,即使它需要解析未来的帧。
#define AVFMT_FLAG_IGNIDX 0x0002 ///< 忽略指数还是下标?(不知道怎么翻译index,了解即可)。
#define AVFMT_FLAG_NONBLOCK 0x0004 ///< 从输入中读包时设置为非阻塞。
#define AVFMT_FLAG_IGNDTS 0x0008 ///< 如果帧同时包含pts和dts则忽略dts。
#define AVFMT_FLAG_NOFILLIN 0x0010 ///< 不要从其他值中推断出任何值,只返回容器中存储的内容。
#define AVFMT_FLAG_NOPARSE 0x0020 ///< 不使用AVParsers, 您还必须设置AVFMT_FLAG_NOFILLIN,因为fillin代码只在帧上工作,不进行解析 -> 没有帧. 此外,如果已禁用了查找帧边界的解析,则帧的seeking(快进后退)也无法工作。
#define AVFMT_FLAG_NOBUFFER 0x0040 ///< 在可能的情况下不缓冲帧。
#define AVFMT_FLAG_CUSTOM_IO 0x0080 ///< 调用者提供了一个自定义的AVIOContext,不要使用avio_close()函数关闭它。
#define AVFMT_FLAG_DISCARD_CORRUPT 0x0100 ///< 丢弃标记为已损坏的帧。
#define AVFMT_FLAG_FLUSH_PACKETS 0x0200 ///< 缓冲AVIOContext的每个包
/**
* 当执行muxing复用(即编码)时,尽量避免在输出中写入任何随机/易失性数据。
* 这包括任何随机id、实时时间戳/日期、muxer版本等。
*
* 此标志主要用于测试。
*/
#define AVFMT_FLAG_BITEXACT 0x0400
#if FF_API_LAVF_MP4A_LATM
#define AVFMT_FLAG_MP4A_LATM 0x8000 ///< 弃用,什么也不做。
#endif
#define AVFMT_FLAG_SORT_DTS 0x10000 ///< 尝试通过dts交错输出数据包(使用此标志可以减慢demuxing解复用的速度)
#define AVFMT_FLAG_PRIV_OPT 0x20000 ///< 通过延迟编解码器的打开来启用私有选项(这可以在所有代码转换后成为默认值)。
#if FF_API_LAVF_KEEPSIDE_FLAG
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< 弃用,什么也不做。
#endif
#define AVFMT_FLAG_FAST_SEEK 0x80000 ///< 启用快速但不精确的seek(快进后退或者查找)的某些格式
#define AVFMT_FLAG_SHORTEST 0x100000 ///< 当最短的流停止时,停止muxing复用(编码)。
#define AVFMT_FLAG_AUTO_BSF 0x200000 ///< 根据muxer解复用器的请求添加位流过滤器
/**
* 从输入中读取的数据的最大大小,用于确定输入容器的格式。
* 只有Demuxing解复用时,由调用者调用avformat_open_input()之前设置。
*
* 实际编码该字段不需要处理。
*/
int64_t probesize;
/**
* avformat_find_stream_info()中从输入读取数据的最大时长(AV_TIME_BASE单位)。
* 只有Demuxing解复用时,由调用者调用avformat_find_stream_info()之前设置。
* 可以设置为0,让avformat使用启发式选择(自动设置)。
*
* 实际编码该字段不需要处理。
*/
int64_t max_analyze_duration;
const uint8_t *key;
int keylen;
unsigned int nb_programs;
AVProgram **programs;
/**
* Forced video codec_id.
* 强制使用视频的编解码id类型。
* Demuxing解复用时: 由用户设计(实际上更多的是解复用时avformat_open_input的参4设置或者根据参2的后缀自动寻找,例如后缀为flv内部会帮你自动查找对应的解码器id,然后avformat_find_stream_info真正的寻找可用的解码器。复用编码时用户自行设计)。
*/
enum AVCodecID video_codec_id;
/**
* 同上,但是笔者很少接触处理音频的,都是分离音频只处理视频。
*/
enum AVCodecID audio_codec_id;
/**
* 字幕的编解码器。
* Demuxing解复用时: 由用户设计。
*/
enum AVCodecID subtitle_codec_id;
/**
* 用于每个流的索引的最大内存量(以字节计)。如果索引超过此大小,则根据需要将整个丢弃,以保持较小的大小(即当流超过该下标就会按情况丢弃后面的流)。这可能导致更慢或更不准确的seeking搜索(取决于demuxer)。
* 对于必须使用完整内存索引的demuxer解复用器将忽略这一点。
*
* - muxing复用: 未使用。
* - demuxing解复用: 由用户设计。
*/
unsigned int max_index_size;
/**
* 缓冲大小,从实时捕获设备获得的帧的最大字节内存大小。
*/
unsigned int max_picture_buffer;
/**
* Number of chapters in AVChapter array.
* AVChapter数组中的章节数。在muxing复用时,章节通常写在文件头,所以nb_chapters通常应该在调用write_header之前初始化。一些muxers复用器(例如mov和mkv)也可以在trailer(预告片,个人认为在这里翻译为尾部更好,因为写帧时是由写头写帧写trailer三个步骤完成)中写章节。
* 要在trailer中写入章节,当write_header被调用时,nb_chapters必须为零,而当write_trailer被调用时,nb_chapters必须为非零。
* - muxing复用: 由用户设计。
* - demuxing解复用: 由libavformat库设置。
*/
unsigned int nb_chapters;
AVChapter **chapters;
/**
* 应用于整个文件的元数据。
*
* - demuxing解复用: 由libavformat库的avformat_open_input()函数设置。
* - muxing复用: 可能由调用者在avformat_write_header()之前设置。
*
* 由libavformat库的avformat_free_context()函数释放。不需要我们处理。
*/
AVDictionary *metadata;
/**
* 流的实际启动时间,从Unix时代开始(1970年1月1日00:00),以微秒为单位。也就是说,流中的pts=0是在这个真实世界时间捕获的。
*
* - muxing: 由调用者在调用avformat_write_header()之前设置。如果设置为0或AV_NOPTS_VALUE,则当前的wall-time(实际时间)将被使用。
* - demuxing: 由libavformat库设置。AV_NOPTS_VALUE如果未知(即该值未知)。那么注意,这个值可能在收到一定数量的帧之后才被知道。
*/
int64_t start_time_realtime;
/**
* 被用于确定帧率在avformat_find_stream_info()函数中。
* 只有在Demuxing解复用时,由调用者在调用avformat_find_stream_info()之前设置。
*/
int fps_probe_size;
/**
* 错误识别; 较高的值将检测出更多的错误,但可能将一些或多或少的有效部分误检测为错误。
* 只有在Demuxing解复用时, 由调用者在调用avformat_open_input()之前设置。
*/
int error_recognition;
/**
* 为I/O层自定义中断回调。
*
* demuxing解复用: 由调用者在调用avformat_open_input()之前设置。
* muxing复用: 由用户在avformat_write_header()之前设置(主要用于AVFMT_NOFILE格式)。如果使用回调函数来打开文件,那么回调函数也应该传递给avio_open2()。
*/
AVIOInterruptCB interrupt_callback;
/**
* 启用调试的标志。
*/
int debug;
#define FF_FDEBUG_TS 0x0001
/**
* 交错的最大缓冲持续时间。
*
* 确保所有流正确交错,av_interleaved_write_frame()将等待,直到每个流至少有一个包,然后才实际将每一个包写入输出文件。当一些流是“稀疏的”(即连续的数据包之间有很大的间隙)时,这可能会导致过度的缓冲。
*
* 这个字段具体指定muxing复用队列中,第一个和最后一个数据包的时间戳的最大差异。不管它是否为所有流都排列了一个包,libavformat都将输出一个包以上。
*
* 只有在Muxing复用时, 由调用者在调用avformat_write_header()之前设置。
*/
int64_t max_interleave_delta;
/**
* *允许非标准和实验性的扩展
* 看AVCodecContext.strict_std_compliance成员
*/
int strict_std_compliance;
/**
* 用于用户检测文件上发生的事件的标志。事件处理后,用户必须清除该标记。
* 和AVFMT_EVENT_FLAG_*是一个组合(与其相关)。
*/
int event_flags;
#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< 调用将导致更新元数据。
/**
* 等待第一个时间戳时要读取的最大数据包数。
* 只有在Decodeing解码时才有用。
*/
int max_ts_probe;
/**
* 在muxing复用(封装)期间避免负的时间戳。
* AVFMT_AVOID_NEG_TS_*常量的任何值。
* 注意,这只在使用av_interleaved_write_frame时有效(实际是interleave_packet_per_dts在使用)。
* - muxing复用(封装): 由用户设置。
* - demuxing解复用(解封装): 未使用。
*/
int avoid_negative_ts;
#define AVFMT_AVOID_NEG_TS_AUTO -1 ///< 目标格式需要时启用
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< 改变时间戳,使它们是非负的
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< 改变时间戳,使它们从0开始
/**
* 运输流id。
* 这将转移到demuxer私有选项。因此没有API/ABI兼容性。
*/
int ts_id;
/**
* 音频预加载(单位微秒)。
* 注意,不是所有格式都支持此功能,如果在不受支持的情况下使用它,可能会发生不可预测的事情。
* - encoding编码: 由用户设置。
* - decoding解码: 未使用。
*/
int audio_preload;
/**
* 以微秒为单位的最大块时间。
* 注意,不是所有格式都支持此功能,如果在不受支持的情况下使用它,可能会发生不可预测的事情。
* - encoding编码: 由用户设置。
* - decoding解码: 未使用。
*/
int max_chunk_duration;
/**
* 以字节为单位的最大块大小。
* 注意,不是所有格式都支持此功能,如果在不受支持的情况下使用它,可能会发生不可预测的事情。
* - encoding编码: 由用户设置。
* - decoding解码: 未使用。
*/
int max_chunk_size;
/**
* 强制使用挂钟(个人认为是实际)时间戳作为包的pts/dts,这在B帧的存在下会有未定义的结果。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int use_wallclock_as_timestamps;
/**
* avio标志,用于强制AVIO_FLAG_DIRECT。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int avio_flags;
/**
* duration字段可以通过多种方式进行估计,该字段可以用于了解如何估计duration。
* - encoding编码: 未使用。
* - decoding解码: 由用户自行读取。
*/
enum AVDurationEstimationMethod duration_estimation_method;
/**
* 在打开流时跳过初始字节。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int64_t skip_initial_bytes;
/**
* 正确的单个时间戳溢出。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
unsigned int correct_ts_overflow;
/**
* 对任意(也是非关键)帧进行强制seeking搜索(位移即快进后退)。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int seek2any;
/**
* 在每个包之后刷新I/O上下文。
* - encoding编码: 由用户设置。
* - decoding解码: 未使用。
*/
int flush_packets;
/**
* 格式探测的得分数(score)。
* 最大的得分数是AVPROBE_SCORE_MAX,它是在demuxer解复用探测格式时设置的。
* - encoding编码: 未使用。
* - decoding解码: 由avformat库设置,被用户自行读取。
*/
int probe_score;
/**
* 用于识别格式时的最大读取字节数。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int format_probesize;
/**
* ','允许解码器的分隔列表。如果是NULL,那么所有都是允许的。
* If NULL then all are allowed
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
char *codec_whitelist;
/**
* ','允许demuxers解复用器的分隔列表。如果是NULL,那么所有都是允许的。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
char *format_whitelist;
/**
* 用于libavformat内部使用的不透明字段。
* 不能被调用者以任何方式访问。
*/
AVFormatInternal *internal;
/**
* IO重新定位标志。
* 这是由avformat库设置当底层IO上下文读指针被重新定位时。例如,当做基于字节的搜索(seeking)。
* Demuxers解复用器可以使用这个标志来检测这些变化。
*/
int io_repositioned;
/**
* 强制视频编解码器。
* 这允许强制使用特定的decoder解码器,即使存在多个相同codec_id的解码器。
* Demuxing解复用: 由用户设置。
*/
AVCodec *video_codec;
/**
* 强制音频编解码器。
* 这允许强制使用特定的decoder解码器,即使存在多个相同codec_id的解码器。
* Demuxing解复用: 由用户设置。
*/
AVCodec *audio_codec;
/**
* 强制字幕编解码器。
* 这允许强制使用特定的decoder解码器,即使存在多个相同codec_id的解码器。
* Demuxing解复用: 由用户设置。
*/
AVCodec *subtitle_codec;
/**
* 强制数据编解码器。
* 这允许强制使用特定的decoder解码器,即使存在多个相同codec_id的解码器。
* Demuxing解复用: 由用户设置。
*/
AVCodec *data_codec;
/**
* 在元数据头中作为填充写入的字节数。
* Demuxing解复用: 未使用。
* Muxing复用: 由用户通过av_format_set_metadata_header_padding设置。
*/
int metadata_header_padding;
/**
* 用户数据。
* 这是一个存放用户某些私有数据的地方。
*/
void *opaque;
/**
* 设备用于与应用程序通信的回调。
*/
av_format_control_message control_message_cb;
/**
* 输出时间戳偏移量,以微秒为单位。
* Muxing复用: 由用户设置。
*/
int64_t output_ts_offset;
/**
* 转储格式分离器。
* 可以是","或"\n "或其他什么。
* - muxing复用: 由用户设置。
* - demuxing解复用: 由用户设置。
*/
uint8_t *dump_separator;
/**
* 强制数据codec_id.
* Demuxing解复用: 由用户设置。
*/
enum AVCodecID data_codec_id;
#if FF_API_OLD_OPEN_CALLBACKS
/**
* 当需要demuxing解复用时,调用它来进一步打开IO上下文。
*
* 这可以由用户应用程序设置,以便在打开url之前对它们执行安全检查。
* 函数的行为应该类似于avio_open2()。 AVFormatContext作为上下文的信息提供,并访问AVFormatContext.opaque私有用户数据。
*
* 如果为NULL,则使用一些简单的检查和avio_open2()一起使用。
*
* 不能直接从avformat库的外部访问。
* 可查看av_format_set_open_cb()
*
* Demuxing解复用: 由用户设置。
*
* 不推荐使用(弃用)io_open和io_close。一般带有attribute_deprecated都是将被弃用的字段。
*/
attribute_deprecated
int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
#endif
/**
* ','允许协议的分隔列表。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
char *protocol_whitelist;
/**
* 用于打开新的IO流的回调。
*
* 无论muxer或demuxer需要打开一个IO流(对于demuxer通常从avformat_open_input(),但是对于某些格式也可以在其他时间发生调用),它都会调用这个回调函数来获取一个IO上下文。
*
* 参1:AVFormatContext类型,即封装上下文。
* 参2:如果成功,新打开的IO上下文应该返回到这里。
* 参3:要打开的url。该url一般是我们要推流的地址。
* 参4:和AVIO_FLAG_*是一个组合(与其相关)。
* 参5:附加选项的字典,具有与avio_open2()中相同的语义。key/value形式,用于设置推流时的一些设置。
*
* 返回0成功, 负数失败(AVERROR)。判断时小于0即为失败。
*
* 注意,某些muxer和demuxer做嵌套,也就是说,它们打开一个或多个额外的内部格式上下文。因此,传递给这个回调的AVFormatContext指针可能与面对调用者的指针不同。(开发时我们只需用它返回的封装上下文,不需要管他内部是否与我们返回的相同)
* 但是,它将具有相同的‘opaque’字段(因为用户相同)。
*/
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);
/**
* ','不允许协议的分隔列表。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
char *protocol_blacklist;
/**
* 流的最大数量。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int max_streams;
/**
* 跳过在estimate_timings_from_pts函数中的duration计算。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int skip_estimate_duration_from_pts;
/**
* 可探测的最大数据包数。
* - encoding编码: 未使用。
* - decoding解码: 由用户设置。
*/
int max_probe_packets;
} AVFormatContext;
2 AVFormatContext中重要的成员(编码常用)
typedef struct AVFormatContext {
ff_const59 struct AVInputFormat *iformat;//输入文件的AVInputFormat结构体,指明码流数据用到封装格式。例如flv,mkv。 可认为是存放文件的头部信息。例如flv的头部。输入时由avformat_open_input()自行设置,我们只需要使用即可。
ff_const59 struct AVOutputFormat *oformat;//输出文件的一些封装格式属性信息。输出时由avformat_alloc_output_context2(&m_outputContext, nullptr, "flv", outUrl.c_str())初始化设置,我们只需要使用即可。
AVIOContext *pb;//网络流,解复用时不需要处理;复用时由我们调用avio_open2内部赋值。
unsigned int nb_streams;//流数组元素个数,一般为2。
AVStream **streams;//流数组,包含视频和音频。
char *url;//解复用时由avformat_open_input()自动设置;复用输出时由avio_open2的参2赋值,实际上在此之前已经由avformat_alloc_output_context2的参4赋值。
int64_t bit_rate;//输入输出文件的总流码流。
int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options);
void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);//这两个函数实际上并不常用,常用的是调用avio_open2。
}
上面看到,我们实际上对于自己需要处理的东西非常的少,都是通过调用函数帮我们进行处理。唯一处理的比较多的是streams数组里的内容。例如打开输出流后需要我们new视频流,初始化编码器(stream->codec->codec)及其上下文(stream->codec)。
3 AVFormatContext的使用例子,下面只给出输出AVFormatContext的初始设置流程,因为输入时直接avformat_open_input打开即可赋值。
//1)实际上是编码最开始的设置,程序中只需要设置一次(可忽略这一步)
av_register_all();
avfilter_register_all();
avformat_network_init();
//2)创建输出上下文
//avformat_alloc_output_context2相当于打开视频并获取信息
int ret = avformat_alloc_output_context2(&m_outputContext, nullptr, "flv", outUrl.c_str());
if (ret < 0){
//error handle
}
//3)new视频流,初始化编码器及其上下文
AVCodecContext *pCodecCtxEnc;//编码器上下文,需要自己初始化
//初始化编码器
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
//new输出流
AVStream *stream = avformat_new_stream(this->m_outputContext, codec);
stream->r_frame_rate = { 25,1 };//存储在AVStream的帧率
pCodecCtxEnc = stream->codec;//获取new流时创建的内存首地址。
//给编码器上下文赋值
pCodecCtxEnc->codec_id = AV_CODEC_ID_H264;
//......下面还有很多pCodecCtxEnc的成员需要初始化,但下面这个必须要写
/*
* 对于H264 AV_CODEC_FLAG_GLOBAL_HEADER:
* 设置则只包含I帧,此时sps pps需要从codec_ctx->extradata读取。
* 不设置则每个I帧都带 sps pps sei,可以通过debug去查看packet->data的数据。
* 存本地文件时不要去设置,让其连同SPS、PPS、SEI等信息写进入,而直播时一般都会设置,例如海康解出的264会带有SPS、PPS这些信息。
*/
if (m_outputContext->oformat->flags & AVFMT_GLOBALHEADER) {
pCodecCtxEnc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//这个非常重要
}
//这里可能还需要手动给pCodecCtxEnc->extradata赋值SPS/PPS。否则视频无法播放。
//4) 打开网络流
//设置rtsp协议延时最大值
AVDictionary *opts = NULL;
char key[] = "max_delay";
char val[] = "500";
int ret = avio_open2(&this->m_outputContext->pb, outUrl.c_str(), AVIO_FLAG_READ_WRITE, nullptr, &opts);
if (ret < 0){
//error handle
}
//5)调用写帧函数
int flag_ = av_interleaved_write_frame(this->m_outputContext, &pkt);//视频和音频都会写,所以最好调用这个
//int flag_ = av_write_frame(this->m_outputContext, &pkt);只写视频数据
if (flag_ < 0) {
//error handle
}
注意:对于有输入文件的,是没有第四步的初始化步骤的,因为它可以通过拷贝输入文件的编解码上下文进行赋值,例如下面雷神的例子。
而我的是没有输入文件的,所以必须要自己初始化赋值。
雷神的例子:
https://blog.csdn.net/leixiaohua1020/article/details/39803457
4 总结
好了,这一篇我们学习了封装(解封装)AVFormatContext结构体并列出重要的成员和输出时的初始化方法,如果你掌握了上面的内容,那么你再学习FFmpeg已经是非常简单的了。
下次我们将继续学习其内部的成员结构体,下班哈哈哈。