10关于FFmpeg各个常见结构体共同有的字段的获取(帧率,码率,时长,时基等)和其它一些信息(封装名,编解码器名等)的获取注意点

10关于FFmpeg各个常见结构体共同有的字段的获取(帧率,码率,时长,时基等)和其它一些信息(封装名,编解码器名等)的获取注意点

实际上这篇是帮助我们如何去获取常见结构体的信息,因为下面这三个信息在多个结构体中都被包含,使用我们需要区分优先获取哪个字段。

1 帧率在哪些常见结构体(9个)中被包含

1)在AVStream中:
AVRational avg_frame_rate;//平均帧率。
AVRational r_frame_rate;//实时帧率。
2)在AVCodecContext中:
AVRational time_base;//该字段与流中的time_base同名,但意思不一样,流中的是与时间相关的时基单位,这里的意思主要是帧率,一般设置为帧率的倒数,例如帧率fps={25,1},那么该字段一般设置为{1,25},但不一定是帧率的倒数。
AVRational framerate;//解码:对于在压缩比特流中存储帧速率值的编解码器,解码器可以在这里导出它。当未知时为{0,1}。编码:可用于将CFR内容的帧率信号发送给编码器。

对于这四者的用法:优先使用流中的帧率,并且优先使用avg_frame_rate,当avg_frame_rate为{x,0}或者{0,1}时使用r_frame_rate。AVCodecContext中的两个帧率不做参考。
用法例子:

//获取帧率,帧率的打印都在流中的两个成员.且应取平均帧率为先,为{x,0}或者{0,1}则取实时帧率
if (inStream->avg_frame_rate.den == 0 || inStream->avg_frame_rate.num == 0 && inStream->avg_frame_rate.den == 1) {
	m_fps = inStream->r_frame_rate.num / inStream->r_frame_rate.den;
}
else {
	m_fps = inStream->avg_frame_rate.num / inStream->avg_frame_rate.den;
}

另外给出一个支持帧率的数组(只做参考):

在AVCodec中:
const AVRational *supported_framerates;//支持帧率的数组(仅视频)。

2 码率(bit_rate)在哪些常见结构体(9个)中被包含

1)在AVFormatContext中:
int64_t bit_rate;//输入输出文件的总流码流。
2)在AVCodecContext中:
int64_t bit_rate;//平均比特率(码率)。encoding编码: 由用户设置; 不用于常量量化编码。decoding解码: 由用户设置, 如果这个码率信息是在流中可用的话(注:AVStream没有码率字段),可能被libavcodec覆盖。

码率bit_rate总共在封装上下文和编解码上下文中含有,均为平均码率.前者代表总流的平均码率,后者为用户设置的编码时的平均码率(有可能被关闭重写)。使用时应该取前者为先。

用法例子:

if (m_in_fmt_ctx->bit_rate <= 0) {
	m_bit_rate = inStream->codecpar->bit_rate / 1000;//转成KB,默认单位为B字节
}
else {
	m_bit_rate = m_in_fmt_ctx->bit_rate / 1000;
}

3 时长(duration)在哪些常见结构体(9个)中被包含

1)在AVFormatContext中:
int64_t duration;//流的时长,精确到微秒即单位AV_TIME_BASE,仅当您不知道单个流的时长和且没有设置过任何一个流的时长才设置此值。如果没有设置,它会自动从AVStream值推断的。
2)在AVStream中:
int64_t duration;//流的时长,上面的值就是从这里获取.
3)在AVPacket中:
int64_t duration;//一帧的显示时长。与上面两个意思不同。

一般获取文件的时长是从AVStream的duration中获取,但是FFmpeg会帮我自动运算,所以我们也可以直接拿AVFormatContext的duration使用。
第三个是一帧的时长,一般是1/帧率。这里不做详细介绍。

用法例子:

//获取输入文件的总时长,实时视频可能是0.两者单位不一样.封装上下文的为内部时基,流中的为流中的时基.
if (inStream->duration <= 0) {
	if (m_in_fmt_ctx->duration > 0) {//有可能是负数
		m_duration = m_in_fmt_ctx->duration / AV_TIME_BASE;//实际上可以直接取这里即可
	}
}
else {
	m_duration = inStream->duration / (inStream->time_base.den);//从time_base时基(den)转成秒(s)
}

4 start_time字段在哪些常见结构体(9个)中被包含

1)在AVFormatContext中
int64_t start_time;//首帧部分的位置,精确到微秒即单位AV_TIME_BASE,千万不要直接设置这个值,它是从AVStream值推导出来的。只有解复用时才由libavformat内部设置。由下面流中的start_time推导赋值。
2)在AVStream中:
int64_t start_time;//Decoding解码:流中首帧的显示时间(即流首帧的pts),单位为流中的时基time_base。只有当你绝对100%确定你设置的值确实是第一帧的pts时才设置这个。(即千万别动该字段)

5 时基(time_base)在哪些常见结构体(9个)中被包含

1)在AVStream中:
AVRational time_base;//输入封装上下文中流的时基,也叫容器的时基,即tbn。注意:tbn一般是针对输入容器的叫法,不针对输出,例如我输入流为flv,那么时基即tbn应为1000,而输出时被转为ts流,那么时基即tbn=90k。所以这点需要区分,dump的时候也有区分这点,即左上角有一个Input,代表输入流的信息。实际上我们转时基也是针对输入封装上下文的视频流和输出封装上下文的视频流的这个time_base,即各自的AVFormatContext->stream-time_base。
2)在AVCodecContext中:
AVRational time_base;//在第一点已经说过,这个字段常被赋值为帧率的倒数,所以通常代表帧率;但是也可以当做时基,因为取倒数就重新变成时基。例如帧率为{1,25},倒数后变成{25,1},即帧率=25,再取倒数变回时基1/25=0.04s单位。即dump函数打印的Input的tbc,也叫流中编解码器的时基即tbr,也有很多人喜欢叫流的时基。

6 获取信息其它字段和获取时的注意点
1)分辨率

//错误写法,直接索引,没有判断m_video_index是否存在
m_in_fmt_ctx->streams[m_video_index]->codecpar->width;
m_in_fmt_ctx->streams[m_video_index]->codecpar->height;

//正确写法,其它的也一样,必须判断下标存在
for (int i = 0; i < m_in_fmt_ctx->nb_streams; i++) {
	AVStream *inStream = NULL;
	inStream = m_in_fmt_ctx->streams[i];
	if (inStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {//可用新版本或者旧版本编解码上下文
		//获取视频下标
		m_video_index = i;

		//获取分辨率
		m_width = inStream->codecpar->width;//必须在保证下标存在的地方获取
		m_height = inStream->codecpar->height;
}

2)获取编解码器类型的名字
由于编解码器在打开流信息后并未初始化,所以必须通过打开编解码器函数来初始化该编解码器才能获取该编解码器的名字。

	//获取编码器类型的名字
	if (m_video_index != -1) {
		AVCodecID codecId;
		AVCodec *codec = NULL;

		//不能通过第一种方法取名字,因为在未调用查找编码器前,编码器是没有被赋值的,会造成非法访问
		//1)video_codec_long_name = in_fmt_ctx->streams[video_index]->codec->codec->long_name;
		codecId = m_in_fmt_ctx->streams[m_video_index]->codecpar->codec_id;
		codec = avcodec_find_decoder(codecId);
		if (codec == NULL) {
			m_video_codec_long_name = "";//表示没有找到这种编解码器
		}
		m_video_codec_long_name = codec->long_name;
	}

3)获取音频的采样率和通道数
因为获取音频的采样率和通道数在解码器前有可能为0,所以我们最好将AVPacket解码成AVFrame,然后获取其结构体内的信息,这里不写出来,只是写出前者的例子,解码成AVFrame网上很多,也不难。

实际上我们看到,很多视频信息的获取都是在遍历AVStream数目时来获取的,保证下标安全并且区分了流的类型。

for (int i = 0; i < m_in_fmt_ctx->nb_streams; i++) {
	AVStream *inStream = NULL;
	inStream = m_in_fmt_ctx->streams[i];
	if (inStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {//可用新版本或者旧版本编解码上下文
		//获取视频下标
		m_video_index = i;
	}
	if (inStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
		m_audio_index = i;
		//这样获取可能为0,最好通过解码后的获取采样率和通道数
		m_audio_samplerate = inStream->codec->sample_rate;
		m_audio_channels = inStream->codec->channels;
	}
}

5 总结
上面四点已经教会我们如何获取视频信息的各个字段了,并且也说了常见结构体中共同有的字段如何获取,非常简单。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在加载数据之前获取视频信息是比较困难的,因为在视频数据加载完成之前,无法获取视频的元数据。不过,你可以通过一些第三方库来获取视频信息,比如FFmpeg.js。FFmpeg.js是一个基于FFmpeg的JavaScript库,可以在浏览器中解码和转码各种视频格式。 以下是一个使用FFmpeg.js获取视频信息的示例代码: ```javascript // 加载FFmpeg.js ffmpeg = createFFmpeg({ log: true, }); // 加载视频文件 const fileInput = document.querySelector('input[type="file"]'); fileInput.addEventListener('change', async (e) => { const file = e.target.files[0]; await ffmpeg.load(); await ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(file)); // 获取视频信息 await ffmpeg.run('-i', 'input.mp4', '-f', 'null', '-'); const output = ffmpeg.lastConsoleMessage; const info = parseOutput(output); // 输出结果 console.log('分辨率:' + info.resolution); console.log('帧率:' + info.fps + 'fps'); console.log('码率:' + info.bitrate + 'Mbps'); console.log('时长:' + info.duration + '秒'); }); // 解析FFmpeg输出的信息 function parseOutput(output) { const regex = /Stream.*Video:.* (\d+)x(\d+).* (\d+) fps.* (\d+) kb\/s.*Duration: (\d+:\d+:\d+\.\d+)/; const match = regex.exec(output); const resolution = match[1] + 'x' + match[2]; const fps = match[3]; const bitrate = Math.round(match[4] / 1000); const duration = parseDuration(match[5]); return { resolution, fps, bitrate, duration }; } // 解析视频时长 function parseDuration(durationStr) { const regex = /(\d+):(\d+):(\d+\.\d+)/; const match = regex.exec(durationStr); const hours = parseInt(match[1]); const minutes = parseInt(match[2]); const seconds = parseFloat(match[3]); return hours * 3600 + minutes * 60 + seconds; } // 将文件转换成Uint8Array async function fetchFile(file) { const reader = new FileReader(); reader.readAsArrayBuffer(file); await new Promise(resolve => reader.onload = resolve); return new Uint8Array(reader.result); } ``` 这个例子中,我们使用FFmpeg.js加载视频文件,并使用FFmpeg命令行工具获取视频信息。解析视频信息的正则表达式可能需要根据不同的视频格式进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值