说明 :将 avfromatContext 的变量依次打印分析,根据ffmpeg 给的说明,猜测,结合网上的文章字节写测试代码分析。
从常用到不常用依次分析
1. unsigned int nb_streams;
代表 avfromatContext 中 AVStream **streams 的个数
/**
* Number of elements in AVFormatContext.streams.
*
* Set by avformat_new_stream(), must not be modified by any other code.
*/
unsigned int nb_streams;
//打印在解复用时候,avformatContext的主要参数
void PrintfDeMuxingAVFormatContextMainParamter(AVFormatContext* avformatContext) {
if (avformatContext == NULL) {
cout << "func PrintfDeMuxingAVFormatContextMainParamter error because AVFormatContext == nullptr " << endl;
return;
}
cout << "avformat中有的avstream的个数为:avformatContext->nb_streams = " << avformatContext->nb_streams << endl;
}
2. int64_t bit_rate;
在 AVFormatContext
结构体中,bit_rate
字段表示媒体文件的全局平均比特率(单位为 bps,即 bits per second)
/**
* Total stream bitrate in bit/s, 0 if not
* available. Never set it directly if the file_size and the
* duration are known as FFmpeg can compute it automatically.
*/
int64_t bit_rate;
2.1 含义再说明1
从说明中可以看出来,代表的是所有 stream 的 平均比特率。啥意思呢?比如这是一个mp4文件,既有视频也有音频,我们假设有一个视频两个音频(粤语和普通话),假设视频是300kbps,普通话音频是128kbps,粤语音频是100kbps。那么 AVFormatContext
中的 bit_rate就应该等于 300 +128+100 = 628kbps,注意单位是kbps。
// 遍历所有流,计算总比特率
int64_t total_bit_rate = 0;
for (int i = 0; i < avformat_ctx->nb_streams; i++) {
AVStream *stream = avformat_ctx->streams[i];
total_bit_rate += stream->codecpar->bit_rate;
}
2.2 含义再说明2,在TS下无效
0 if not available
这个意思是说,这个值有可能是0,代表不可使用。
部分封装格式(如 TS 流)可能不记录全局比特率,此时 bit_rate
字段无效
2.3 含义再说明3,当文件大小和duration都知道的时候,user不要设置,ffmepg会自动计算
Never set it directly if the file_size and the duration are known as FFmpeg can compute it automatically.
2.5 动态码率场景
VBR 编码的文件中,该字段仅代表平均值,无法反映瞬时码率波动
CBR 是 (恒定比特率)
VBR(可变比特率),
mp4文件只能是 VBR。
也就是说:如果我们解析的是mp4文件,那么这个值是平均值。无法反映瞬时码率波动
2.6 典型应用场景
- 带宽估算:
结合容器和流的比特率,判断网络传输是否满足实时播放需求56。 - 文件分析工具:
统计媒体文件的码率分布,辅助编码参数优化6。
3. int64_t duration
只能用于解复用,
是 AVFormatContext 结构体的关键成员,用于表示 媒体文件的总时长。其数据类型为 int64_t
,单位为 AV_TIME_BASE(即微秒的倒数,通常为 1,000,000)。
该字段在 成功解析媒体文件流信息后(调用 avformat_find_stream_info()
)才会被正确赋值
/**
* Duration of the stream, in AV_TIME_BASE fractional
* seconds. Only set this value if you know none of the individual stream
* durations and also do not set any of them. This is deduced from the
* AVStream values if not set.
*
* Demuxing only, set by libavformat.
*/
int64_t duration;
主要使用场景,通过 duration 计算文件时长。
换算为秒:
double duration_sec = (double)avformatContext->duration / AV_TIME_BASE;
转换为时分秒格式:
int64_t total_us = avformatContext->duration + 5000; // 四舍五入修正
int hours = total_us / (3600 * AV_TIME_BASE);
int mins = (total_us % (3600 * AV_TIME_BASE)) / (60 * AV_TIME_BASE);
int secs = (total_us % (60 * AV_TIME_BASE)) / AV_TIME_BASE;
常见问题与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
返回负值或极大值 | 未正确解析流信息或文件不完整 | 调用 avformat_find_stream_info() 前设置 max_analyze_duration 参数 |
单位换算错误 | 未使用 AV_TIME_BASE 进行转换 | 确保除以 AV_TIME_BASE (或使用 av_rescale_q() 函数) |
时间精度丢失 | 直接截断未四舍五入 | 添加 5000 微秒偏移(如 +5000 )后再计算 |
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, filename, NULL, NULL); // 打开文件
fmt_ctx->max_analyze_duration = 5 * AV_TIME_BASE; // 限制解析时长避免卡顿
avformat_find_stream_info(fmt_ctx, NULL); // 解析流信息
if (fmt_ctx->duration != AV_NOPTS_VALUE) {
int64_t duration = fmt_ctx->duration + 5000; // 修正精度
int hours = duration / (3600 * AV_TIME_BASE);
int mins = (duration % (3600 * AV_TIME_BASE)) / (60 * AV_TIME_BASE);
int secs = (duration % (60 * AV_TIME_BASE)) / AV_TIME_BASE;
printf("Duration: %02d:%02d:%02d\n", hours, mins, secs);
} else {
printf("Duration unavailable\n");
}
avformat_close_input(&fmt_ctx); // 释放资源
适用场景
- 媒体信息分析工具:如
ffprobe
使用该字段输出文件时长。 - 播放器开发:用于显示进度条总时长。
- 流媒体处理:结合
AVStream
中各流时长进行同步控制。
注意:在网络流或实时流中,duration
可能无法获取(值为 AV_NOPTS_VALUE
),需动态计算
cout << "avformat中duration为:avformatContext->duration = " << avformatContext->duration << endl;
double duration_sec = (double)avformatContext->duration / AV_TIME_BASE;
cout << "avformat中秒数为:duration_sec = " << duration_sec << endl;
if (avformatContext->duration != AV_NOPTS_VALUE) {
int64_t duration = avformatContext->duration + 5000; // 修正精度
int hours = duration / (3600 * AV_TIME_BASE);
int mins = (duration % (3600 * AV_TIME_BASE)) / (60 * AV_TIME_BASE);
int secs = (duration % (60 * AV_TIME_BASE)) / AV_TIME_BASE;
printf("Duration: %02d:%02d:%02d\n", hours, mins, secs);
}else {
printf("Duration unavailable\n");
}
avformat中duration为:avformatContext->duration = 60024000
avformat中秒数为:duration_sec = 60.024
Duration: 00:01:00
4. char *url;
/**
* input or output URL. Unlike the old filename field, this field has no
* length restriction.
*
* - demuxing: set by avformat_open_input(), initialized to an empty
* string if url parameter was NULL in avformat_open_input().
* - muxing: may be set by the caller before calling avformat_write_header()
* (or avformat_init_output() if that is called first) to a string
* which is freeable by av_free(). Set to an empty string if it
* was NULL in avformat_init_output().
*
* Freed by libavformat in avformat_free_context().
*/
char *url;
和之前的filename不同,url是没有长度限制的。
在解码时,通过 avformat_open_input 方法 会将url 记录到 AVFormatContext ,可能会nullptr。
在编码时,需要在 调用 avformat_write_header 方法之前设置。
char * url = avformatContext->url;
cout << "avformat中duration为 url = " << url << endl;
结果为:avformat中duration为 url = ./120/400_300_25.mp4
5. int64_t start_time;
/**
* Position of the first frame of the component, in
* AV_TIME_BASE fractional seconds. NEVER set this value directly:
* It is deduced from the AVStream values.
*
* Demuxing only, set by libavformat.
*/
int64_t start_time;
组件第一帧的位置,以AV_TIME_BASE 为单位。
切勿直接设置此值:它是从AVStream值推断出来的。
这玩意有啥用呢?表示该avformatContext 第一帧的开始时间,那么应该都是0。
可能的点:todo
如果我们从文件的中间位置读取的,那么这个值就不是0?
在网络流的时候用?
int64_t starttime = avformatContext->start_time;
cout << "avformat中duration为 starttime = " << starttime << endl;
avformat中duration为 starttime = 0
6. 接下来都是非重点 AVCodec* audio_codec;
/**
* Forced audio codec.
* This allows forcing a specific decoder, even when there are multiple with the same codec_id.
* Demuxing: Set by user
*/
AVCodec *audio_codec;
* 强制 音频 编解码器,
* 在解复用的时候,允许强制使用特定的解码器,即使存在多个具有相同codec_id的解码器
我们先看一下在解析mp4的时候,打印结果:是为nullptr的。
我们这里将 avformatContext中的audio_code_id也打印出来,结果为0.
cout << "-------------------------" << endl;
AVCodec* audioavcodec = avformatContext->audio_codec;
if (audioavcodec == nullptr) {
cout << "audioavcodec == nullptr" << endl;
}
else {
cout << "audioavcodec != nullptr audioavcodec->id = " << audioavcodec->id << endl;
}
AVCodecID audioavcodecid = avformatContext->audio_codec_id;
cout << "audioavcodecid =" << audioavcodecid << endl;
cout << "-------------------------" << endl;
log 为:
audioavcodec == nullptr
audioavcodecid =0
我们按照翻译中字面的意思,在decoder的时候,强制使用特定的解码器 。我们写一个test02测试一下。
参考代码如下,从例子中可以看出来,我们强行使用mp3 去解析视频文件中的音频,一定是会报错的。如果要对,那么前提是开发者知道 音频文件的格式,采样率,声道等信息。如果不知道,我们可以从 stream[audio_index]->codepar->codec_id 中获得真正的 audio decoder,按照正常的流程解码
那么这个audio_codec 再我看来,在解码过程中很鸡肋。这里记录一下流程,可能在其他自己还没有想到的场景下用到。测试也说明,我们下一个要学习的
void test02() {
const char* url_400 = "./120/400_300_25.mp4";
//这里指定avformatContext 的 audiocodec 就是mp3格式的
AVFormatContext* avformatContext = avformat_alloc_context();
AVCodec *audio_avcodec_MP3 = avcodec_find_decoder(AV_CODEC_ID_MP3);
avformatContext->audio_codec = audio_avcodec_MP3;
int ret = avformat_open_input(&avformatContext, url_400, nullptr, nullptr);
if (ret < 0) {
cout << "func avformat_open_input error" << endl;
PrintErr(ret);
return;
}
cout << "avformatContext->audio_codec = " << avformatContext->audio_codec << endl;// 79950010
cout << "avformatContext->audio_codec->id = " << avformatContext->audio_codec->id << endl;// 86017.... 86017 就是 AV_CODEC_ID_MP3
int audioindex = -1;
int videoindex = -1;
// audioindex = av_find_best_stream(avformatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
// videoindex = av_find_best_stream(avformatContext, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
//audioindex = av_find_best_stream(avformatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, &audio_avcodec_mp3, 0);
//videoindex = av_find_best_stream(avformatContext, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
for (int i = 0; i < avformatContext->nb_streams; i++) {
if (avformatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
}
if (avformatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioindex = i;
}
}
cout << "audioindex = " << audioindex << endl;
cout << "videoindex = " << videoindex << endl;
//这里打印stream[audioindex] 中的 codeid,发现是 AAC 的。。注意是 stream[audioindex]->codecpar->codec_id
cout << "avformatContext->streams[audioindex]->codecpar->codec_id = " << avformatContext->streams[audioindex]->codecpar->codec_id << endl; // 86018 ...... 86018 就是 AV_CODEC_ID_AAC
cout << "avformatContext->streams[videoindex]->codecpar->codec_id = " << avformatContext->streams[videoindex]->codecpar->codec_id << endl;
// 试图打印 stream[audioindex]->codec,AVCodecContext *codec;attribute_deprecated 发现这个已经废弃,有build error
//avformatContext->streams[audioindex]->codec->codec_id;// build error,提示 codec 已经废弃
//按照 avformatContext 中 关于 AVCodec *audio_codec 的说明,在解码的时候用到,那么先回想一下解码时候的一般写法
//AVPacket* avpacket = av_packet_alloc();
//AVFrame* avframe = av_frame_alloc();
//AVCodec* mp3avcodec111 = avcodec_find_decoder(AV_CODEC_ID_MP3);
//AVCodecContext* avcodeccontext111 = avcodec_alloc_context3(mp3avcodec111);
//avcodec_open2(avcodeccontext111, mp3avcodec111, nullptr);
//avcodec_send_packet(avcodeccontext111, avpacket);
//avcodec_receive_frame(avcodeccontext111, avframe);
//按照 avformatContext 中 关于 AVCodec *audio_codec 的说明,在解码的时候用到.
// 这里是为了解码做准备
//如果我们设定了 avformatContext->audio_codec = audio_avcodec_mp3;,那么解码的时候肯定是要用这试一试
AVCodecContext* avcodeccontext222 = avcodec_alloc_context3(avformatContext->audio_codec);
avcodeccontext222->sample_fmt = AV_SAMPLE_FMT_S16;
avcodeccontext222->sample_rate = 44100;
avcodeccontext222->channels = 2;
avcodeccontext222->channel_layout = 2;
avcodec_open2(avcodeccontext222, avformatContext->audio_codec, nullptr);
//这里理论上要把 AVCodecParameters 拷贝到 AVCodecContext,但是 AVCodecParameters 是 aac的,avcodeccontext222 是mp3的
//那么就有问题了了,这里我们的就需要再次设置 audio的参数 ,返回到上一层,通过 avcodeccontext222设置 sample_fmt,sample_rate,channels,注意,这里的前提是:你怎么知道你要解码音频文件的 三要素,否则会出错。
//AVCodecParameters* audio_AVCodecParameters = avformatContext->streams[audioindex]->codecpar;
//avcodec_parameters_to_context(avcodeccontext222, audio_AVCodecParameters);
cout << "理论上到这里我们就设置完成了" << endl;
AVPacket* avpacket = av_packet_alloc();
AVFrame* avframe = av_frame_alloc();
while (true) {
ret = av_read_frame(avformatContext, avpacket);
if (ret <0 ) {
cout << "av_read_frame error return " << endl;
break;
}
if (avpacket->stream_index == audioindex) {
// 读取到的是 音频的avpacket,
cout << "avpacket->size = " << avpacket->size << endl;
//ret = avcodec_send_packet(avformatContext->streams[audioindex]->codec, avpacket);//build error 提示 codec 已经废弃
ret = avcodec_send_packet(avcodeccontext222, avpacket);
if (ret < 0) {
cout << "avcodec_send_packet error return " << endl; //
PrintErr(ret);
continue;
}
else {
while (true) {
ret = avcodec_receive_frame(avcodeccontext222, avframe);
if (ret < 0) {
cout << "avcodec_receive_frame error return " << endl;
PrintErr(ret);
break;
}
else if (ret == 0) {
//pcm 数据
cout << "read pcm data" << endl;
}
}
}
}
else if (avpacket->stream_index == videoindex) {
// 读取到的是 视频的avpacket
}
av_packet_unref(avpacket);
}
av_packet_free(&avpacket);
av_frame_free(&avframe);
cout << "指定avfromatContext 的 audio_avcodec 是AV_CODEC_ID_AAC, end" << endl;
}
7. enum AVCodecID audio_codec_id;
/**
* Forced audio codec_id.
* Demuxing: Set by user.
*/
enum AVCodecID audio_codec_id;
我们先看一下在解析mp4的时候,打印结果:是为nullptr的。
我们这里将 avformatContext中的audio_code_id也打印出来,结果为0.
cout << "-------------------------" << endl;
AVCodec* audioavcodec = avformatContext->audio_codec;
if (audioavcodec == nullptr) {
cout << "audioavcodec == nullptr" << endl;
}
else {
cout << "audioavcodec != nullptr audioavcodec->id = " << audioavcodec->id << endl;
}
AVCodecID audioavcodecid = avformatContext->audio_codec_id;
cout << "audioavcodecid =" << audioavcodecid << endl;
cout << "-------------------------" << endl;
log 为:
audioavcodec == nullptr
audioavcodecid =0
8. AVCodec *video_codec;
/**
* Forced video codec.
* This allows forcing a specific decoder, even when there are multiple with
* the same codec_id.
* Demuxing: Set by user
*/
AVCodec *video_codec;
看这个说明和 6 很像,应该也是鸡肋一样的存在
9.enum AVCodecID video_codec_id
/**
* Forced video codec_id.
* Demuxing: Set by user.
*/
enum AVCodecID video_codec_id;
10 AVCodec *subtitle_codec;
/**
* Forced subtitle codec.
* This allows forcing a specific decoder, even when there are multiple with
* the same codec_id.
* Demuxing: Set by user
*/
AVCodec *subtitle_codec;
11.enum AVCodecID subtitle_codec_id;
/**
* Forced subtitle codec_id.
* Demuxing: Set by user.
*/
enum AVCodecID subtitle_codec_id;
6--11 总结
在解码mp4的时候,log 如下,都是0或者nullptr
都是可以指定的具体的 音频/视频/字幕 解码器,
比较鸡肋。建议忽略
cout << "-------------------------" << endl;
AVCodec* audioavcodec = avformatContext->audio_codec;
if (audioavcodec == nullptr) {
cout << "audioavcodec == nullptr" << endl;
}
else {
cout << "audioavcodec != nullptr audioavcodec->id = " << audioavcodec->id << endl;
}
AVCodecID audioavcodecid = avformatContext->audio_codec_id;
cout << "audioavcodecid =" << audioavcodecid << endl;
cout << "-------------------------" << endl;
AVCodec* videoavcodec = avformatContext->video_codec;
if (videoavcodec == nullptr) {
cout << "videoavcodec == nullptr" << endl;
}
else {
cout << "videoavcodec != nullptr videoavcodec->id = " << videoavcodec->id << endl;
}
AVCodecID videoavcodecid = avformatContext->video_codec_id;
cout << "videoavcodecid =" << videoavcodecid << endl;
cout << "-------------------------" << endl;
AVCodec* subtitleavcodec = avformatContext->subtitle_codec;
if (subtitleavcodec == nullptr) {
cout << "subtitleavcodec == nullptr" << endl;
}
else {
cout << "subtitleavcodec != nullptr subtitleavcodec->id = " << subtitleavcodec->id << endl;
}
AVCodecID subtitleavcodecid = avformatContext->subtitle_codec_id;
cout << "subtitleavcodecid =" << subtitleavcodecid << endl;
cout << "-------------------------" << endl;
12 int audio_preload;
/**
* Audio preload in microseconds.
* Note, not all formats support this and unpredictable things may happen if it is used when not supported.
* - encoding: Set by user
* - decoding: unused
*/
int audio_preload;
中文翻译
*音频预加载(微秒)。
*请注意,并非所有格式都支持此功能,如果在不支持的情况下使用,可能会发生不可预测的事情。
*-编码:由用户设置
解码:不使用
应用场景未知。
cout << "avformatContext->audio_preload = " << avformatContext->audio_preload << endl;// 结果为0
13 int avio_flags;
/**
* avio flags, used to force AVIO_FLAG_DIRECT.
* - encoding: unused
* - decoding: Set by user
*/
int avio_flags;
中文翻译
*avio标志,用于强制avio_FLAG_DIRECT。
*-编码:未使用
*-解码:由用户设置
*/
avio_flags在AVFormatContext中用于控制输入/输出流的缓冲方式。具体来说,avio_flags是一个标志位,用于指定输入/输出流的缓冲行为。
avio_flags 的作用和设置方法
avio_flags可以设置不同的标志来控制缓冲方式,常见的标志包括:
'direct':减少缓冲,提高数据传输效率。
应用场景 未知,但是从说明看起来 应该是 在 解码时,不需要缓冲 的场景时可以设置
avformatContext->avio_flags;
cout << "avformatContext->avio_flags = " << avformatContext->avio_flags << endl;// 当前结果为0
//AVIO_FLAG_DIRECT; 0x8000
AVFormatContext*ic = avformat_alloc_context();
ic->avio_flags = AVIO_FLAG_DIRECT; // 设置直接缓冲方式
/**
* Use direct mode.
* avio_read and avio_write should if possible be satisfied directly
instead of going through a buffer, and avio_seek will always
call the underlying seek function directly.
*/
#define AVIO_FLAG_DIRECT 0x8000
/**
*使用直接模式。
*如果可能的话,应直接满足avio_read和avio_write的要求
而不是通过缓冲区,avio_seek将始终
直接调用底层seek函数。
*/
14 int avoid_negative_ts
该成员变量在 复用 过程中使用。
有三个值AVFMT_AVOID_NEG_TS_AUTO, AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE , AVFMT_AVOID_NEG_TS_MAKE_ZERO
该值在在使用av_interleaved_write_frame时有效。(interleave_packet_per_dts正在使用中)
/**
* Avoid negative timestamps during muxing.
* Any value of the AVFMT_AVOID_NEG_TS_* constants.
* Note, this only works when using av_interleaved_write_frame. (interleave_packet_per_dts is in use)
* - muxing: Set by user
* - demuxing: unused
*/
int avoid_negative_ts;
#define AVFMT_AVOID_NEG_TS_AUTO -1 ///< Enabled when required by target format
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< Shift timestamps so that they start at 0
*在复用过程中避免使用负时间戳。
*AVFMT_AVOID_NEG_TS_*常数的任何值。
*注意,这仅在使用av_interleaved_write_frame时有效。(interleave_packet_per_dts正在使用中)
*-复用:由用户设置
*-解复用:未使用
*/
int avoid_negative_ts;
#定义AVFMT_AVID_NEG_TS_AUTHO-1///<目标格式要求时启用
#定义AVFMT_AVIOD_NEG_TS_MAKE_NON_NEGATIVE 1///<移位时间戳,使其为非负
#定义AVFMT_AVIOD_NEG_TS_MAKE_ZERO 2///<移位时间戳,使其从0开始
要明白应用场景,就要知道 av_interleaved_write_frame 函数是干啥的
原型:
int av_interleaved_write_frame(AVFormatContext *fmt_ctx, AVPacket *pkt);
1. 核心功能
- 数据排序与同步
该函数将编码后的AVPacket
按时间戳(DTS)排序后写入输出文件或流,确保音视频数据的正确交错(interleaving)和播放顺序。 - 自动缓冲机制
内部通过缓存和调整数据包的时序,避免因乱序写入导致播放异常
2. 参数说明
fmt_ctx
: 输出文件的格式上下文(AVFormatContext
),包含输出流和容器格式信息23。pkt
: 待写入的AVPacket
,需包含编码后的数据及时间戳(PTS/DTS)等元信息23。
3. 返回值
0
表示成功,负值为错误码(如AVERROR_EOF
或AVERROR(EAGAIN)
)24。
4. 内部工作机制
- 时间戳处理
自动修正数据包的dts
和pts
,确保输出文件的时间戳连续性。 - 比特流过滤
调用do_packet_auto_bsf
函数处理比特流过滤逻辑(如 H.264 的 Annex B 格式转换)。 - 交错排序算法
若容器未实现自定义交错逻辑,默认使用ff_interleave_packet_per_dts
按 DTS 排序。
5. 典型使用场景
- 流媒体输出
与avio_open
结合使用,支持 RTMP/RTSP 等协议的实时推流5。 - 多轨道媒体文件
适用于包含音视频混合的 MP4/MKV 等格式,防止因写入顺序错误导致播放器解析失败24。 - 网络传输优化
在服务器断开时需检查编码参数或网络连接状态5。
6. 与 av_write_frame
的区别
特性 | av_interleaved_write_frame | av_write_frame |
---|---|---|
数据排序 | 自动按 DTS 排序并缓冲 | 直接写入,无排序逻辑 |
适用场景 | 需严格时序的多轨道文件或流媒体 | 用户自行管理交错顺序的场景 |
性能开销 | 较高(需缓存和排序) | 较低 |
引用计数处理 | 自动接管 AVPacket 引用所有权 | 需手动管理引用34 |
7. 注意事项
- 引用计数
若AVPacket
是引用计数的,函数会接管其所有权并在适当时机释放37。 - 错误处理
返回AVERROR(EAGAIN)
时需继续调用直至数据写入完成4。 - 头尾写入
需在avformat_write_header
和av_write_trailer
之间调用此函数48。
此方法一看就是要有了avpacket的时候,就调用一次,我们在写入文件的时候测试一下。 todo
15 AVChapter **chapters;
/**
* Number of chapters in AVChapter array.
* When muxing, chapters are normally written in the file header,
* so nb_chapters should normally be initialized before write_header
* is called. Some muxers (e.g. mov and mkv) can also write chapters
* in the trailer. To write chapters in the trailer, nb_chapters
* must be zero when write_header is called and non-zero when
* write_trailer is called.
* - muxing: set by user
* - demuxing: set by libavformat
*/
unsigned int nb_chapters;
AVChapter **chapters;
在 FFmpeg 的 AVFormatContext
结构中,AVChapter **chapters
成员的作用及相关特性如下:
1. 基本定义
- 类型:双重指针
AVChapter**
,指向动态数组的指针,存储媒体文件中的章节信息6。 - 内存管理:需通过
avformat_alloc_context()
初始化AVFormatContext
后访问此成员3。
2. 章节数据结构
每个 AVChapter
包含以下核心字段:
- 时间范围:
start
和end
时间戳(基于time_base
时间基)6。 - 元数据:通过
AVDictionary
存储章节标题、语言等附加信息6。 - 唯一标识:
id
字段标识章节的索引顺序6。
3. 典型应用场景
- 多媒体文件处理:
用于解析或生成 MKV、MP4 等支持章节标记的格式文件25。 - 交互式播放控制:
播放器可通过章节信息实现快速跳转或分段播放5。 - 元数据编辑:
允许修改章节标题、时间范围等属性后重新封装文件2。
4. 操作示例
AVFormatContext *fmt_ctx = avformat_alloc_context(); // 初始化上下文
// 遍历章节
for (int i = 0; i < fmt_ctx->nb_chapters; i++) {
AVChapter *chapter = fmt_ctx->chapters[i];
// 获取章节开始时间(秒)
double start_sec = chapter->start * av_q2d(chapter->time_base);
}
5. 注意事项
- 生命周期:
chapters
内存由AVFormatContext
自动管理,调用avformat_close_input()
或avformat_free_context()
时释放37。 - 格式支持:并非所有容器格式均支持章节(如 FLV 不支持),需通过
AVOutputFormat
的flags
校验26。
该成员是处理带章节的多媒体文件时的重要扩展功能,需结合具体容器格式特性使用25。
应用场景:猜想的,看到bilibili 中有跳转到某一个具体位置,猜测就是这个方法在调转。
unsigned int nb_chapters = avformatContext->nb_chapters;
AVChapter** chapters = avformatContext->chapters;
cout << "avformatContext->nb_chapters = " << nb_chapters << endl;
if (chapters == nullptr) {
cout << "avformatContext->chapters = nullptr" << endl;
}
else {
for (int i = 0; i < nb_chapters; i++ ) {
int64_t start = chapters[i]->start;
int64_t end = chapters[i]->end;
AVRational time_base = chapters[i]->time_base;
int id = chapters[i]->id;
AVDictionary* metadata = chapters[i]->metadata;
}
}
16 unsigned int nb_chapters;
参考15,表示AVChapter **chapters的个数,
17 char *codec_whitelist;
/**
* ',' separated list of allowed decoders.
* If NULL then all are allowed
* - encoding: unused
* - decoding: set by user
*/
char *codec_whitelist;
在解复用时限制可以选择的解码器,但是baidu发现,应该在 复用 时也可以使用
关于 AVFormatContext
中的 char *codec_whitelist
成员,其作用及使用场景如下:
1. 基本定义
- 类型:字符串指针 (
char*
),用于指定允许使用的编解码器白名单7。 - 作用范围:主要在解复用(demuxing)过程中限制可选择的解码器,或在复用(muxing)时限制编码器类型7。
2. 主要用途
- 编解码器过滤:
通过设置以逗号分隔的编解码器名称(如"h264,aac"
),强制 FFmpeg 仅使用白名单内的编解码器处理数据流7。 - 安全限制:
防止加载潜在不兼容或存在安全风险的编解码器(尤其在处理不可信输入源时)7。 - 性能优化:
减少编解码器探测时间,提升处理效率(例如在实时推流场景中)7。
3. 设置方法
- 初始化时指定:
可通过av_dict_set
函数设置选项参数,示例:cCopy Code
AVFormatContext *fmt_ctx = avformat_alloc_context(); av_dict_set(&fmt_ctx->metadata, "codec_whitelist", "h264,aac", 0);
- 动态修改:
在打开输入/输出流后,通过修改fmt_ctx->codec_whitelist
直接赋值(需注意生命周期管理)7。
4. 注意事项
- 格式兼容性:
白名单需与容器格式支持的编解码器匹配(如 MP4 容器需指定h264
而非libx264
)7。 - 优先级规则:
若同时设置黑名单 (codec_blacklist
),白名单优先生效7。 - 释放内存:
字符串内存需手动管理,避免内存泄漏(通过av_free
释放)7。
此成员适用于需要精确控制编解码器选择的场景(如安全敏感型应用或资源受限环境),但需结合具体容器格式特性使用7。
char* codec_whitelist = avformatContext->codec_whitelist;
if (codec_whitelist == nullptr) {
cout << "avformatContext->codec_whitelist = nullptr" << endl;
}
else {
cout << "codec_whitelist = " << codec_whitelist << endl;
}
18 av_format_control_message control_message_cb;
设备和 应用 通讯的callback 函数。
没有找到例子,ffmpeg 源码中也没有例子,baidu不到相关的信息。
ffmpeg的声明写的很简单,就是 设备 和 应用程序的 callback。
/**
* Callback used by devices to communicate with application.
*/
av_format_control_message control_message_cb;
/**
* Callback used by devices to communicate with application.
*/
typedef int (*av_format_control_message)(struct AVFormatContext *s, int type,
void *data, size_t data_size);
查了一下源码,如下的部分是猜测。
发现在avdevice.c中有实现,看名字,应该是有两个实现,都会用到,一个是 app to dev。一个是dev to app。如果遇到实际情况的case,应该沿着这条 线看下去。
int avdevice_app_to_dev_control_message(struct AVFormatContext *s, enum AVAppToDevMessageType type,
void *data, size_t data_size)
{
if (!s->oformat || !ffofmt(s->oformat)->control_message)
return AVERROR(ENOSYS);
return ffofmt(s->oformat)->control_message(s, type, data, data_size);
}
int avdevice_dev_to_app_control_message(struct AVFormatContext *s, enum AVDevToAppMessageType type,
void *data, size_t data_size)
{
if (!s->control_message_cb)
return AVERROR(ENOSYS);
return s->control_message_cb(s, type, data, data_size);
}
cout << "avformatContext->control_message_cb = " << avformatContext->control_message_cb<<endl; 值为nullptr
参考28 的字段 int event_flags, 结合起来学习
19、unsigned int correct_ts_overflow
在解复用的时候,纠正 单个时间戳 溢出。
/**
* Correct single timestamp overflows
* - encoding: unused
* - decoding: Set by user
*/
unsigned int correct_ts_overflow;
cout << " avformatContext->correct_ts_overflow = " << avformatContext->correct_ts_overflow << endl; 值是1
问题是:
1.什么时候有单个 时间戳的溢出?
2.为了纠正,应该设置什么样的值?
todo
20 int ctx_flags;
看翻译,和 stream info 相关
标记信号流属性。AVFMTCTX_*的组合。
/* stream info */
/**
* Flags signalling stream properties. A combination of AVFMTCTX_*.
* Set by libavformat.
*/
int ctx_flags;
#define AVFMTCTX_NOHEADER 0x0001 /**< signal that no header is present
(streams are added dynamically) */
#define AVFMTCTX_UNSEEKABLE 0x0002 /**< signal that the stream is definitely
not seekable, and attempts to call the
seek function will fail. For some
network protocols (e.g. HLS), this can
change dynamically at runtime. */
打印log 值是0.
cout << "avformatContext->ctx_flags = " << avformatContext->ctx_flags << endl; // 结果为0
ctx_flags:标志,表示流属性。AVFMTCTX_*的组合。一般在read_header中设置
AVFMTCTX_NOHEADER 0x0001 表示不存在header(流是动态添加的)
AVFMTCTX_UNSEEKABLE 0x0002 表示流绝对不可定位,尝试调用seek函数将失败。
这个是有啥应用场景呢?
todo
21 AVCodec *data_codec;
/**
* Forced data codec.
* This allows forcing a specific decoder, even when there are multiple with
* the same codec_id.
* Demuxing: Set by user
*/
AVCodec *data_codec;
参考6-11,应该也是没啥用的变量
22 enum AVCodecID data_codec_id
/**
* Forced Data codec_id.
* Demuxing: Set by user.
*/
enum AVCodecID data_codec_id;
参考6-11,应该也是没啥用的变量
23 int debug;
开启 debug 的选项,打印默认值是0,理论上关闭的。设置为1表示开启,
ffmpeg给了 宏定义FF_FDEBUG_TS
/**
* Flags to enable debugging.
*/
int debug;
#define FF_FDEBUG_TS 0x0001
默认值打印,结果是0
int debug = avformatContext->debug;
cout << "avformatContext->debug = " << avformatContext->debug << endl; ;
这个有啥用呢?或者说,开启后是debug 那个模块
24 uint8_t *dump_separator;
dump_separator
是用于控制元数据或流信息输出格式的分隔符参数
/**
* dump format separator.
* can be ", " or "\n " or anything else
* - muxing: Set by user.
* - demuxing: Set by user.
*/
uint8_t *dump_separator;
打印结果为: avformatContext->dump_separator = ,
uint8_t* dump_separator = avformatContext->dump_separator;
if (dump_separator == nullptr) {
cout << "avformatContext->dump_separator = nullptr" << endl; ;
}
else {
cout << "avformatContext->dump_separator = " << avformatContext->dump_separator << endl; ;
}
在 FFmpeg 框架中,dump_separator
是用于控制元数据或流信息输出格式的分隔符参数,主要应用于调试或结构化数据导出场景37。以下是其核心用法及关联机制:
1. 参数作用与适用场景
- 元数据格式化
指定分隔符对ffmpeg
或ffprobe
输出的元数据字段进行分割(默认使用换行符或空格)37。 - 脚本处理适配
当需要将输出结果导入脚本或数据库时,可自定义分隔符(如逗号,
或竖线|
)以提高解析效率3。 - 调试日志增强
结合-v verbose
日志级别使用,可清晰区分不同层次的调试信息7。
2. 命令行调用示例
通过 -dump_separator
指定分隔符格式:
bashCopy Code
ffmpeg -i input.mp4 -dump_separator " | " -f null -
此命令将输出流信息时以 |
分隔各字段,例如分辨率、码率等属性37。
3. 高级配置选项
- 多字符支持
允许使用多字符组合作为分隔符(如"###"
),增强可读性7。 - 转义字符兼容
支持\n
(换行)、\t
(制表符)等转义字符,适配不同平台需求7。 - 与
-show_entries
联动
在ffprobe
中配合-show_entries
选择输出字段,生成结构化报表:bashCopy Code
ffprobe -v error -show_streams -dump_separator "," input.mp4
4. 注意事项
- 版本兼容性
该参数在 FFmpeg 4.0 及以上版本中稳定支持,旧版本可能需通过源码编译启用37。 - 容器格式限制
部分封装格式(如 MPEG-TS)的流信息输出可能忽略此分隔符设置3。 - 编码器差异
仅影响元数据输出,不影响编码过程中的时间戳或数据包处理7。
如需更精细控制输出格式,可结合 -print_format
指定 JSON、XML 等结构化格式替代纯文本
要结合元数据学习,下面就结合元数据的学习一下
简单说:元数据就是 如下的数据,这些对于视频的管理很重要。对应的 AVFormatContext 中的就是AVDictionary *metadata;
标题(title): 描述音视频文件的主题或内容。
艺术家(artist): 创建或表演音视频内容的艺术家或团体的名称。
专辑(album): 音视频文件所属的专辑或系列名称。
流派(genre): 描述音视频内容的音乐或视频类型。
年份(year): 音视频内容的创作或发布年份。
版权(copyright): 关于版权的信息。
描述(description): 对音视频内容的简短描述。
分辨率(resolution): 视频的宽度和高度。
帧率(frame rate): 视频每秒显示的帧数。
编码格式(codec): 音视频数据的编码方式。
————————————————
25 AVDictionary *metadata;
/**
* Metadata that applies to the whole file.
*
* - demuxing: set by libavformat in avformat_open_input()
* - muxing: may be set by the caller before avformat_write_header()
*
* Freed by libavformat in avformat_free_context().
*/
AVDictionary *metadata;
适用于整个文件的元数据。解复用:由libavformat在avformat_open_input()中设置
, 复用:可以在avformat_write_header()之前由用户设置,由libavformat在avformat_free_context()中释放。
在 FFmpeg 的 AVFormatContext
结构中,AVDictionary *metadata
字段用于存储文件级别的元数据信息,包含视频、音频或容器格式的附加描述信息35。以下是其核心机制与操作方式:
1. 数据结构与作用
- 全局元数据存储
metadata
以键值对(key-value
)形式保存容器格式的全局元数据,如标题、作者、版权信息等。 - 与流级元数据区分
每个AVStream
结构体也有独立的metadata
字段,用于存储该特定流(如音频流、视频流)的元数据。
2. 常见元数据键名
FFmpeg 支持标准化的元数据键名,例如:
"title"
:媒体标题"artist"
:创作者或艺术家"album"
:所属专辑"date"
:创建日期"copyright"
:版权声明"comment"
:注释信息
3. 元数据操作接口
- 元数据访问
使用av_dict_get()
函数遍历元数据:cCopy Code
AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(fmt_ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { printf("%s: %s\n", tag->key, tag->value); }
- 元数据修改
通过av_dict_set()
动态增删或修改键值对:cCopy Code
av_dict_set(&fmt_ctx->metadata, "author", "Custom Author", 0);
4. 应用场景
- 媒体信息展示
提取元数据用于播放器界面显示媒体属性(如时长、分辨率、比特率)3。 - 转码保留元数据
在转码或复用(remux)时,通过复制metadata
保留原始文件的描述信息6。 - 自定义元数据注入
生成新文件时添加自定义元数据(如 GPS 坐标、设备型号)5。
5. 注意事项
- 编码器与容器支持
部分编码器或容器格式(如 MP4、FLV)可能限制特定元数据的写入或读取56。 - 字符编码兼容性
元数据内容需统一使用 UTF-8 编码,避免乱码问题3。 - 内存管理
AVFormatContext
销毁时(如调用avformat_close_input()
),metadata
会自动释放,无需手动清理37。
示例:读取文件元数据
//遍历
AVDictionary* metadata = avformatContext->metadata;
int count = av_dict_count(metadata);
cout << "av_dict_count(metadata)" << count << endl;
if (metadata == nullptr) {
cout << "avformatContext->metadata = nullptr" << endl;
}
else {
AVDictionaryEntry* entry = NULL;
while ((entry = av_dict_get(metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
printf("Key: %s, Value: %s\n", entry->key, entry->value);
}
}
//找到具体的某一个值
cout << "-----111-------" << endl;
AVDictionary* dict = NULL;
av_dict_set(&dict, "video_codec", "h264", 0);
av_dict_set(&dict, "audio_codec", "aac", 0);
AVDictionaryEntry* entry = NULL;
while ((entry = av_dict_get(dict,"video_codec", entry, AV_DICT_IGNORE_SUFFIX))) {
printf("Key: %s, Value: %s\n", entry->key, entry->value);
}
av_dict_free(&dict);
cout << "-----222-------" << endl;
av_dict_count(metadata)4
Key: major_brand, Value: isom
Key: minor_version, Value: 512
Key: compatible_brands, Value: isomiso2avc1mp41
Key: encoder, Value: Lavf58.45.100
-----111-------
Key: video_codec, Value: h264
-----222-------
完整学习参考
av_dict_get,av_dict_set,av_dict_set_int-CSDN博客
26 enum AVDurationEstimationMethod duration_estimation_method;
和duration的计算方式有关。先参考 3 int64_t duration 回顾 duration 的用处。
duration 是 这视频的长度,ffmpeg 内部在计算 duration的时候,有多种参数可以使用,这个枚举就是让我们选择用啥参数
视频的持续时间可以通过各种方式估算,并且可以使用此枚举
/**
* The duration field can be estimated through various ways, and this field can be used
* to know how the duration was estimated.
* - encoding: unused
* - decoding: Read by user
*/
enum AVDurationEstimationMethod duration_estimation_method;
/**
* The duration of a video can be estimated through various ways, and this enum can be used
* to know how the duration was estimated.
*/
enum AVDurationEstimationMethod {
AVFMT_DURATION_FROM_PTS, ///< Duration accurately estimated from PTSes 0
AVFMT_DURATION_FROM_STREAM, ///< Duration estimated from a stream with a known duration 1
AVFMT_DURATION_FROM_BITRATE ///< Duration estimated from bitrate (less accurate) 2
};
AVFMT_DURATION_FROM_PTS,/// 根据PTS准确估计的持续时间
AVFMT_DURATION_FROM_STREAM,/// 根据已知持续时间的流估计的持续时间
AVFMT_DURATION_FROM_BITRATE/// 根据比特率估计的持续时间(精度较低)
当前 mp4 打印出来结果为1,根据AVFMT_DURATION_FROM_STREAM计算出来的
cout << "avformatContext->duration_estimation_method = " << avformatContext->duration_estimation_method << endl;
27 int error_recognition;
解复用前面,可以通过设置这个值,检测到更多的错误。,但是可能会导致误检测,感觉没啥用
/**
* Error recognition; higher values will detect more errors but may
* misdetect some more or less valid parts as errors.
* Demuxing only, set by the caller before avformat_open_input().
*/
int error_recognition;
*错误识别;较高的值将检测到更多的错误,但可能会将一些或多或少有效的部分误检测为错误。
*仅限解复用,由调用者在avformat_open_input()之前设置。
默认值是1
cout << "avformatContext->error_recognition = " << avformatContext->error_recognition << endl;
28 int event_flags;
/**
* Flags for the user to detect events happening on the file. Flags must
* be cleared by the user once the event has been handled.
* A combination of AVFMT_EVENT_FLAG_*.
*/
int event_flags;
#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata.
打印默认值,结果为1
cout << "avformatContext->event_flags = " << avformatContext->event_flags << endl; //结果为1
AVFMT_EVENT_FLAG_METADATA_UPDATED
的功能解析
AVFMT_EVENT_FLAG_METADATA_UPDATED
是 FFmpeg 中用于标识 媒体元数据动态更新 的事件标志,常见于流媒体或实时封装格式(如 MPEG-TS、FLV)的解封装过程中78。以下是其核心特性与使用场景:
1. **标志作用与触发条件
- 元数据变更通知:当解封装器(Demuxer)在解析过程中检测到元数据(如标题、分辨率、语言轨道等)发生变化时,触发此标志7。
- 动态媒体支持:适用于直播流或动态调整媒体参数的场景,例如:
- 直播中切换分辨率或码率;
- 多语言音轨的动态加载8。
2. **关联接口与使用逻辑
-
事件回调机制:
通过注册AVFormatContext
的 事件回调函数(如av_format_control_message
)捕获此事件,具体流程如下:- 初始化
AVFormatContext
并设置回调函数; - 在解封装循环中,通过
av_read_frame()
读取数据包; - 当元数据变更时,FFmpeg 内部触发
AVFMT_EVENT_FLAG_METADATA_UPDATED
事件,回调函数接收通知7。
- 初始化
-
代码示例片段:
// 定义事件回调函数
static int metadata_event_callback(AVFormatContext *s, int type, void *data, size_t data_size) {
if (type & AVFMT_EVENT_FLAG_METADATA_UPDATED) {
printf("Metadata updated!\n");
// 重新读取元数据(如 s->metadata)
}
return 0;
}
// 初始化时绑定回调
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx->control_message_callback = metadata_event_callback;
3. **典型应用场景
- 直播流自适应:实时检测分辨率或编码参数变化,动态调整解码器或渲染逻辑8。
- 多语言/字幕切换:当媒体流新增音轨或字幕时,通知应用程序更新可选轨道列表7。
- 动态广告插入:直播流中插入广告时,元数据可能变更,需同步更新播放器信息8。
4. **兼容性与限制
- 格式支持差异:
- 部分封装格式(如 MP4)通常静态存储元数据,此事件可能不会触发;
- 流媒体协议(如 HLS、RTMP)更易触发此事件78。
- 资源消耗:频繁的元数据更新可能增加回调处理的开销,需优化逻辑避免性能问题。
总结
AVFMT_EVENT_FLAG_METADATA_UPDATED
是 FFmpeg 处理动态元数据变更的关键机制,通过与事件回调结合,可实现对实时媒体流的精细化控制。开发者需根据封装格式特性合理设计回调逻辑,并注意性能优化78。
29 char filename[1024]; 已废弃,有build error
avformatContext->filename; //attribute_deprecated char filename[1024];已废弃,有build error,已经使用url 替代
30 int flags;
/**
* Flags modifying the (de)muxer behaviour. A combination of AVFMT_FLAG_*.
* Set by the user before avformat_open_input() / avformat_write_header().
*/
int flags;
打印结果为 2097152
cout << "avformatContext->flags = " << avformatContext->flags << endl;
对应的16进制为:
#define AVFMT_FLAG_AUTO_BSF 0x200000 ///< Add bitstream filters as requested by the muxer
AVFormatContext
的 flags
字段解析
AVFormatContext
的 flags
字段用于控制封装/解封装过程中的行为模式,其取值由多个标志位组合而成,直接影响媒体文件的解析与生成逻辑8。以下是关键信息整理:
1. **核心功能
- 流程控制:通过标志位定义输入输出的处理策略,例如:
- 是否自动生成缺失的时间戳(
AVFMT_FLAG_GENPTS
)8; - 是否启用快速非精确的 Seek 模式(
AVFMT_FLAG_FAST_SEEK
)8; - 是否允许网络协议的非阻塞模式(
AVFMT_FLAG_NONBLOCK
)8。
- 是否自动生成缺失的时间戳(
- 格式适配:针对特定封装格式的兼容性调整,如 TS 流的拼接模式(
AVFMT_FLAG_FLAT_PES
)8。
2. **常用标志位
标志位名称 | 作用描述 |
---|---|
AVFMT_FLAG_GENPTS | 自动生成缺失的 PTS(Presentation Time Stamp)值,确保时间戳连续性8。 |
AVFMT_FLAG_FAST_SEEK | 启用快速 Seek 模式(牺牲精度换取速度,适用于实时流场景)8。 |
AVFMT_FLAG_NONBLOCK | 设置 I/O 操作为非阻塞模式,避免因网络延迟导致的线程阻塞8。 |
AVFMT_FLAG_FLAT_PES | 将 PES 包数据扁平化处理,适用于 TS 流拼接8。 |
AVFMT_FLAG_KEEP_SIDE_DATA | 保留流中的附加元数据(如 H.264 SEI 信息)8。 |
3. **典型场景与设置示例
-
直播流处理:
组合使用AVFMT_FLAG_NONBLOCK
和AVFMT_FLAG_FAST_SEEK
,优化实时性要求高的场景8。cCopy Code
format_ctx->flags |= AVFMT_FLAG_NONBLOCK | AVFMT_FLAG_FAST_SEEK;
-
修复时间戳问题:
启用AVFMT_FLAG_GENPTS
自动填充缺失的 PTS/DTS:cCopy Code
avformat_open_input(&format_ctx, filename, NULL, NULL); format_ctx->flags |= AVFMT_FLAG_GENPTS;
4. **注意事项
- 默认值:大多数场景下,FFmpeg 会根据封装格式自动设置合理标志位,无需手动干预8。
- 冲突处理:标志位可能存在互斥性(如网络协议与本地文件操作),需结合具体协议类型设置8。
总结
AVFormatContext
的 flags
字段通过标志位组合控制媒体处理的底层行为,需根据协议类型、实时性需求等场景灵活配置,以确保解析与封装的高效性和兼容性8
31int flush_packets
/**
* Flush the I/O context after each packet.
* - encoding: Set by user
* - decoding: unused
*/
int flush_packets;
//打印值为 -1
cout << "avformatContext->flush_packets = " << avformatContext->flush_packets << endl;
从翻译的看来,是在复用的时候使用,但是baidu发现可能在解复用的时候也能用到。
AVFormatContext
的 flush_packets
字段解析
flush_packets
是 AVFormatContext
中控制 解封装(Demux)过程中内部缓存刷新行为 的字段,主要用于优化 Seek 操作或实时流场景下的数据一致性46。
1. **核心功能
- 缓存刷新控制:
-
flush_packets = 1
:强制立即清空解封装器内部的缓存数据包(如执行 Seek 操作后丢弃旧数据,确保后续读取的数据来自新位置)46。 -
flush_packets = 0
(默认):保留缓存数据,复用未处理的包以提高效率(适用于静态文件或非实时场景)48。
-
2. **典型应用场景
- 精确 Seek 操作:
在跳转播放位置时,避免残留的旧数据包干扰新位置的数据读取,例如视频播放器中拖动进度条后需要立即刷新缓存68。 - 低延迟实时流处理:
强制丢弃过期的数据包,减少因缓存累积导致的延迟(如直播流中需要实时处理最新帧)48。
3. **代码示例
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx->flush_packets = 1; // 启用强制刷新
// Seek 到指定时间戳
av_seek_frame(format_ctx, -1, target_ts, AVSEEK_FLAG_BACKWARD);
// 后续读取的数据将基于新位置
AVPacket pkt;
av_read_frame(format_ctx, &pkt);
4. **注意事项
- 性能影响:
频繁启用flush_packets
会增加 I/O 开销(需重新解析数据),降低解封装效率,建议仅在必要场景下开启48。 - 默认行为:
大多数静态文件(如 MP4、MKV)默认禁用此选项,以复用缓存数据优化性能48。 - 兼容性:
对实时流协议(如 RTMP、HLS)支持较好,部分本地文件格式可能不支持动态刷新逻辑68。
总结
flush_packets
通过控制内部缓存的刷新机制,解决了 Seek 和实时流场景下的数据一致性问题,开发者需根据具体场景权衡性能与实时性需求46。
32 int format_probesize; 用于识别 format。在 avformat_open_input方法中使用
/**
* number of bytes to read maximally to identify format.
* - encoding: unused
* - decoding: set by user
*/
int format_probesize;
在 avformat_open_input 方法中使用,用于识别 format,也就是音频 的格式 AAC 或者视频的 格式 YUV420P 。
当前打印结果为1m,是当前的默认值,这个默认值是根据系统不同决定的,有可能在linux下是5m。
//avformatContext->format_probesize = 1048576 单位是字节,1048576字节/1024 = 1024 kb ;1024kb /1024 = 1M,也就是说,默认,可以探测的最大字节为1M
cout << "avformatContext->format_probesize = " << avformatContext->format_probesize << endl;
format_probesize 字段
在解复用的时候使用,我们知道 avformat_open_input()方法和avformat_find_stream_info()方法在解复用的开始就需要调用,avformat_open_input()方法是解析 mp4等有文件头的,avformat_find_stream_info()方法是解析 h264等没有文件头的。目的都是找到文件的 元数据信息。
问题是,这个format_probesize字段的大小,仅是 avformat_open_input方法中生效,还是 avformat_find_stream_info中也生效,还是在哪里生效?
通过查看源码知道
format_probesize字段是在avformat_open_input()方法的时候就使用的,也就是说,在解析
在 avformat_find_stream_info方法实现中没有看到使用。
format_probesize
是 AVFormatContext
结构体中的一个成员变量,它用于指定在尝试打开文件时,FFmpeg 应该读取的最大字节数来探测文件格式。这个参数对于自动检测文件格式非常重要,特别是在文件头部没有足够的格式信息(如扩展名不正确或者文件损坏)的情况下。
作用
-
格式探测:当FFmpeg尝试打开一个文件时,它首先会读取一定数量的字节(由
format_probesize
指定)来尝试识别文件的格式。例如,如果设置了较大的format_probesize
,FFmpeg会读取更多的数据来尝试更准确地确定文件的格式,这有助于处理一些边缘情况,如文件头部有损坏或格式不明显的情况。 -
性能考虑:增加
format_probesize
可以提高格式探测的准确性,但同时也会增加读取和处理数据的时间。因此,这个值需要根据具体情况进行调整,以平衡准确性和性能。
AVFormatContext
的 format_probesize
字段解析
format_probesize
是 AVFormatContext
中用于 控制媒体格式探测阶段读取的数据量大小 的关键参数,直接影响封装格式的识别精度与效率16。
1. **核心功能
- 数据探测范围:
定义 FFmpeg 在初始化解封装时分析的最大数据量(单位:字节),用于判断媒体文件的封装格式(如 MP4、FLV、TS 等)16。- 值越大:探测范围更广,可提高复杂或分散格式的识别成功率(例如多流混合或元数据分散的媒体文件)6。
- 值越小:减少 I/O 延迟,但可能导致格式探测失败(数据不足无法匹配特征)1。
2. **默认值与调整
- 默认值:
5,000,000
字节(约 5MB),适用于绝大多数场景16。 - 手动设置:
cCopy Code
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx->format_probesize = 10 * 1024 * 1024; // 设为 10MB avformat_open_input(&format_ctx, filename, NULL, NULL);
3. **典型应用场景
- 网络流探测:
对于实时流(如 RTMP、HLS),若初始数据包中格式信息分散,需增大format_probesize
以确保正确识别15。 - 非常规封装格式:
自定义或非标准格式可能需扩展探测范围以提取足够特征6。
4. **注意事项
- 性能权衡:
增大此值会延长初始化解封装的时间,尤其在网络传输或高延迟存储中更明显16。 - 探测失败处理:
若格式识别错误或返回AVERROR_INVALIDDATA
,可尝试逐步增加format_probesize
并重试6。
总结
format_probesize
通过控制探测数据量平衡格式识别的精度与效率,需根据媒体类型(本地文件/网络流)和封装复杂度灵活调整15。
33 char *format_whitelist;
/**
* ',' separated list of allowed demuxers.
* If NULL then all are allowed
* - encoding: unused
* - decoding: set by user
*/
char *format_whitelist;
指定 解复用器 白名单,如果为nullptr 表示 所有的 解码器都可以。
当前测试结果为nullptr
//测试结果为 format_whitelist == nullptr
char* format_whitelist = avformatContext->format_whitelist;
if (format_whitelist == nullptr) {
cout << "format_whitelist == nullptr" << endl;
}
else {
cout << "avformatContext->format_whitelist = " << avformatContext->format_whitelist << endl;
}
format_whitelist
字段是 AVFormatContext
结构体中的一个属性,它在FFmpeg的版本更新中引入,主要用于限制可以解析和编码的媒体格式。
功能和用途
format_whitelist
字段允许用户指定一个格式列表,FFmpeg只会考虑这个列表中的格式进行解码或编码。这对于安全考虑特别有用,比如防止恶意构造的文件导致安全漏洞。例如,如果你的应用只需要处理MP4和MKV文件,你可以通过设置 format_whitelist
来确保FFmpeg不会尝试解析或编码其他类型的文件,这样可以减少潜在的安全风险。
如何使用
在FFmpeg的API中,你可以通过设置 AVFormatContext
的 format_whitelist
字段来指定允许的格式。下面是一个简单的示例代码,展示如何设置和使用 format_whitelist
:
#include <libavformat/avformat.h>
int main() {
avformat_network_init(); // 初始化网络组件(如果需要)
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (!fmt_ctx) {
fprintf(stderr, "Could not allocate format context\n");
return -1;
}
// 设置 format_whitelist
fmt_ctx->format_whitelist = "mp4,mkv"; // 只允许MP4和MKV格式
// 其他代码,例如打开文件、读取流等
// ...
avformat_free_context(fmt_ctx); // 释放资源
avformat_network_deinit(); // 反初始化网络组件(如果需要)
return 0;
}
注意事项
-
在使用
format_whitelist
前,确保已经初始化了FFmpeg库。 -
确保在不需要时释放所有分配的资源,以避免内存泄漏。
-
format_whitelist
应该只包含你确实需要处理的格式名称,以避免不必要的性能开销和潜在的安全问题。 -
从FFmpeg版本4.0开始引入了
format_whitelist
功能。如果你使用的是更早的版本,这个功能可能不可用或者需要通过其他方式实现类似的功能(例如通过命令行参数或环境变量)。
通过合理使用 format_whitelist
,你可以提高你的应用程序处理媒体文件时的安全性和效率。
34 int fps_probe_size;在解复用中计算video的fps。在avformat_find_stream_info方法中使用
/**
* The number of frames used for determining the framerate in
* avformat_find_stream_info().
* Demuxing only, set by the caller before avformat_find_stream_info().
*/
int fps_probe_size;
打印一下,结果为 avformatContext->fps_probe_size = -1
cout << "avformatContext->fps_probe_size = " << avformatContext->fps_probe_size << endl;
在 解复用的时候,设置读取多少帧,才能计算出 video的fps。如果不设置,默认值是-1;为啥是-1?看源码。发现在解复用的时候,真的用多少帧,是由于很多值决定的。且开始的时候,会赋值一个20.
-
解码时:
fps_probe_size
用于指定在尝试探测视频流的真实帧率时,应该查看多少帧。这对于某些编码格式,特别是那些不直接在头部信息中明确标明帧率的格式(如 MPEG-TS),非常有用。通过查看连续几帧的时间戳差值,可以估算出实际的帧率。 -
编码时,不一定有用
编码时:虽然主要用于解码时的帧率探测,但在某些情况下,编码器也可能使用这个参数来设置期望的帧率(例如,通过设置 muxer 的时间基)。但通常,编码器的帧率设置是通过其他方式(如编码器特定的选项)来控制的。
核心功能定位
-
fps_probe_size
:
用于 帧率(FPS)探测阶段的数据量控制,指定在分析视频流时需读取的初始数据量,以计算准确的帧率值。- 适用场景:当视频流头部未明确包含帧率信息(如某些实时流或自定义封装格式时),需通过一定量的数据样本推导帧率。
- 调整影响:增大此值可能提升帧率计算精度,但会增加初始解析时间。
参见源码,源码中这块很复杂,
在 int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)方法中。
设置为25帧,通过25帧计算 fps,也就是25帧的平均fps
AVFormatContext* formatCtx = avformat_alloc_context();
if (!formatCtx) {
fprintf(stderr, "Could not allocate format context\n");
exit(1);
}
// 设置 fps_probe_size 为 25,例如
formatCtx->fps_probe_size = 25;
35 int64_t probesize; 在解复用中使用,avformat_find_stream_info 方法中使用
/**
* Maximum size of the data read from input for determining
* the input container format.
* Demuxing only, set by the caller before avformat_open_input().
*/
int64_t probesize;
//avformatContext->probesize = 5000000,单位是字节 。 probe_size(format_probesize)默认约为 5MB(5,000,000 字节)
cout << "avformatContext->probesize = " << avformatContext->probesize << endl;
36 int64_t max_analyze_duration; 解复用时使用,在avformat_find_stream_info 方法中使用
/**
* Maximum duration (in AV_TIME_BASE units) of the data read
* from input in avformat_find_stream_info().
* Demuxing only, set by the caller before avformat_find_stream_info().
* Can be set to 0 to let avformat choose using a heuristic.
*/
int64_t max_analyze_duration;
*从avformat_find_stream_info()中的输入读取数据的最大持续时间(以AV_TIME_BASE为单位)。
*仅限解复用,由调用者在avformat_find_stream_info()之前设置。
*可以设置为0,让avformat使用heuristic(启发式方法)进行选择。
max_analyze_duration
是 FFmpeg 中 AVFormatContext
结构体的关键字段,主要用于控制 流信息探测阶段的时长和数据量,直接影响媒体元数据(如编码格式、帧率、分辨率等)的解析效率和准确性17。
1. 核心功能
- 作用阶段:在调用
avformat_find_stream_info()
时生效,用于限制 FFmpeg 解析流信息的最大时间或数据量18。 - 控制目标:决定探测阶段读取的媒体数据时长(单位:微秒),确保在合理时间内获取流参数,避免因数据量过大导致延迟78。
2. 参数设置与影响
属性 | 说明 |
---|---|
默认值 | 通常为 5 * AV_TIME_BASE (即 5 秒) |
单位 | 微秒(需通过 AV_TIME_BASE 宏转换,例如 5 * AV_TIME_BASE 表示 5 秒) |
调优场景 | 网络流、实时流等需要快速起播的场景,可适当减小值以减少探测时间;非标准格式或复杂流需增大值以提高解析成功率 |
负面影响 | 值过小可能导致流信息解析失败(如无法获取帧率或编码参数);值过大可能增加初始延迟78 |
3. 代码示例
AVFormatContext *fmt_ctx = avformat_alloc_context();
avformat_open_input(&fmt_ctx, input_file, NULL, NULL);
// 设置最大分析时长(例如设置为 2 秒)
fmt_ctx->max_analyze_duration = 2 * AV_TIME_BASE;
// 启动流信息探测
avformat_find_stream_info(fmt_ctx, NULL);
4. 关联参数
-
probesize
:限制探测阶段读取的最大数据量(字节数),与max_analyze_duration
共同控制探测行为的资源消耗。 -
fps_probe_size
(若存在):专用于帧率探测的数据量控制,优先级低于max_analyze_duration
。
5. 优化建议
- 实时流场景:将
max_analyze_duration
设为1 * AV_TIME_BASE
(1 秒)以内,结合probesize
减小探测数据量,实现快速起播78。 - 复杂流处理:若流头部信息不完整,需增大此值(如
10 * AV_TIME_BASE
)以提高格式识别成功率78。 - 已知流格式时:直接通过
AVCodecParameters
设置解码器参数,跳过探测阶段以提升效率7。
总结
max_analyze_duration
是流媒体解析性能调优的核心参数,需根据场景平衡 解析精度 与 响应速度,结合 probesize
参数实现最佳效果17。
32-36 总结
int format_probesize; 在解复用中用于识别 format。在 avformat_open_input方法中使用。默认值是1M 或者5M,
- 值越大:探测范围更广,可提高复杂或分散格式的识别成功率(例如多流混合或元数据分散的媒体文件)。
- 值越小:减少 I/O 延迟,但可能导致格式探测失败(数据不足无法匹配特征)。
int fps_probe_size;在解复用中计算video的fps。在avformat_find_stream_info方法中使用。看源码,一般情况下默认值应该是20.
- 适用场景:当视频流头部未明确包含帧率信息(如某些实时流或自定义封装格式时),需通过一定量的数据样本推导帧率。
- 调整影响:增大此值可能提升帧率计算精度,但会增加初始解析时间。可以调整为25
- 计算25帧的数据,以计算fps
int64_t probesize; 在解复用中使用,avformat_find_stream_info 方法中使用。
默认值 是 5000000字节,也就是大约 5M
调整影响:增大此值在解复用时,多解析字节。但是会有I/O延迟。减小可以快一些。
int64_t max_analyze_duration; 解复用时使用,在avformat_find_stream_info 方法中使用.
如果是0,则ffmpeg 使用启发式方式自动设置。
一般默认值是 通常为 5 * AV_TIME_BASE
(即 5 秒)
调优场景 | 网络流、实时流等需要快速起播的场景,可适当减小值以减少探测时间;非标准格式或复杂流需增大值以提高解析成功率 |
负面影响 | 值过小可能导致流信息解析失败(如无法获取帧率或编码参数);值过大可能增加初始延迟 |
优化建议
- 实时流场景:将
max_analyze_duration
设为1 * AV_TIME_BASE
(1 秒)以内,结合probesize
减小探测数据量,实现快速起播。 - 复杂流处理:若流头部信息不完整,需增大此值(如
10 * AV_TIME_BASE
)以提高格式识别成功率。 - 已知流格式时:直接通过
AVCodecParameters
设置解码器参数,跳过探测阶段以提升效率7。