音视频入门基础:WAV专题(7)——FFmpeg源码中计算WAV音频文件每个packet的size值的实现

embedded/2024/12/23 5:53:40/

一、引言

从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以显示WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的size:

693ed79c59f94ce6af9b7593d45124df.png

这个“size”实际是AVPacket结构体中的成员变量size,为WAV音频文件中某个packet的大小(单位为字节),通过fftools/ffprobe.c中的show_packet函数打印出来:

static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...print_val("size",             pkt->size, unit_byte_str);
//...
}

本文讲述这个“size”值是怎样被计算出来的。如果想直接看结论,可以跳到本文的最后,直接看“总结”。

二、FFmpeg源码中计算WAV音频文件每个packet的size值的实现

(一)ff_pcm_default_packet_size函数

size值其实是通过源文件libavformat/pcm.c中的ff_pcm_default_packet_size函数计算出来的:

int ff_pcm_default_packet_size(AVCodecParameters *par)
{int nb_samples, max_samples, bits_per_sample;int64_t bitrate;if (par->block_align <= 0)return AVERROR(EINVAL);max_samples = INT_MAX / par->block_align;bits_per_sample = av_get_bits_per_sample(par->codec_id);bitrate = par->bit_rate;/* Don't trust the codecpar bitrate if we can calculate it ourselves */if (bits_per_sample > 0 && par->sample_rate > 0 && par->ch_layout.nb_channels > 0)if ((int64_t)par->sample_rate * par->ch_layout.nb_channels < INT64_MAX / bits_per_sample)bitrate = bits_per_sample * (int64_t)par->sample_rate * par->ch_layout.nb_channels;if (bitrate > 0) {nb_samples = av_clip64(bitrate / 8 / PCM_DEMUX_TARGET_FPS / par->block_align, 1, max_samples);nb_samples = 1 << av_log2(nb_samples);} else {/* Fallback to a size based method for a non-pcm codec with unknown bitrate */nb_samples = av_clip(4096 / par->block_align, 1, max_samples);}return par->block_align * nb_samples;
}

从《音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现》中可以知道:

par->bit_rate为从WAV Header解码出来的音频码率,单位为bits/s。

par->bits_per_coded_sample为从WAV Header解码出来的音频采样位数。

par->channels为从WAV Header解码出来的声道数量。

par->sample_rate为从WAV Header解码出来的音频采样频率,单位为Hz。

par->block_align为从WAV Header解码出来的“区块对齐”,即每个采样点所需的字节数。

ff_pcm_default_packet_size函数中,首先计算出“最大采样”:

max_samples = INT_MAX / par->block_align;

将拿到的音频采样位数保存到变量bits_per_sample中;把拿到的音频码率(单位为bits/s)保存到变量bitrate中:

bits_per_sample = av_get_bits_per_sample(par->codec_id);
bitrate = par->bit_rate;

如果满足条件:从WAV Header中解码出来的音频采样位数、音频采样频率、声道数量都大于0,不使用从WAV Header中解码出来的音频码率,而是根据公式:音频码率 = 采样位数*采样频率*声道,计算:

    /* Don't trust the codecpar bitrate if we can calculate it ourselves */if (bits_per_sample > 0 && par->sample_rate > 0 && par->ch_layout.nb_channels > 0)if ((int64_t)par->sample_rate * par->ch_layout.nb_channels < INT64_MAX / bits_per_sample)bitrate = bits_per_sample * (int64_t)par->sample_rate * par->ch_layout.nb_channels;

宏PCM_DEMUX_TARGET_FPS定义在源文件libavformat/pcm.c中:

#define PCM_DEMUX_TARGET_FPS  10

关于av_clip、av_clip64用法可以参考:《FFmpeg源码:av_clip、av_clip64宏定义分析》、《FFmpeg源码:av_log2函数分析》。

nb_samples为一帧音频数据中采样的数量(次数)。

情况一:如果音频码率大于0,计算上述音频码率(单位为bits/s) ‌÷ 8 ‌÷ 10 ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,然后求该值是2的多少次幂,保存到变量nb_samples中;

情况二:如果音频码率不大于0,计算4096  ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,保存到变量nb_samples中:

if (bitrate > 0) {nb_samples = av_clip64(bitrate / 8 / PCM_DEMUX_TARGET_FPS / par->block_align, 1, max_samples);nb_samples = 1 << av_log2(nb_samples);} else {/* Fallback to a size based method for a non-pcm codec with unknown bitrate */nb_samples = av_clip(4096 / par->block_align, 1, max_samples);}

最后返回“区块对齐” × 一帧音频数据中采样的次数:

return par->block_align * nb_samples;

(二)wav->max_size

从《音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现》中可以知道,FFmpeg源码通过wav_read_header函数解码WAV Header,该函数最后会调用set_max_size函数:

/* wav input */
static int wav_read_header(AVFormatContext *s)
{
//...WAVDemuxContext *wav = s->priv_data;set_max_size(st, wav);return 0;
//...
}

set_max_size函数定义在源文件libavformat/wavdec.c中。可以看到该函数内部会调用ff_pcm_default_packet_size函数,把“区块对齐” × 一帧音频数据中采样的次数的结果赋值给变量max_size。如果max_size小于0,wav->max_size=4096,否则wav->max_size=“区块对齐” × 一帧音频数据中采样的次数:

static void set_max_size(AVStream *st, WAVDemuxContext *wav)
{if (wav->max_size <= 0) {int max_size = ff_pcm_default_packet_size(st->codecpar);wav->max_size = max_size < 0 ? 4096 : max_size;}
}

(三)AVPacket结构体得到size值

对于WAV音频文件,FFmpeg源码通过源文件libavformat/wavdec.c的wav_read_packet函数读取一个packet:

static int wav_read_packet(AVFormatContext *s, AVPacket *pkt)
{
//...WAVDemuxContext *wav = s->priv_data;
//...left = wav->data_end - avio_tell(s->pb);
//...size = wav->max_size;if (st->codecpar->block_align > 1) {if (size < st->codecpar->block_align)size = st->codecpar->block_align;size = (size / st->codecpar->block_align) * st->codecpar->block_align;}size = FFMIN(size, left);ret  = av_get_packet(s->pb, pkt, size);if (ret < 0)return ret;pkt->stream_index = 0;return ret;
}

由《音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现》中可以知道,wav->data_end为该WAV文件的总大小(单位为字节)。avio_tell(s->pb)为读取到该WAV音频文件的第几个字节了(关于avio_tell函数用法可以参考:《FFmpeg源码:avio_tell函数分析》)。所以wav_read_packet函数中,变量left的值等于该WAV音频文件中还剩下多少个字节没被读取:

left = wav->data_end - avio_tell(s->pb);

让变量size拿到wav->max_size的值,也就是“区块对齐” × 一帧音频数据中采样的次数的结果:

size = wav->max_size;

如果“区块对齐” × 一帧音频数据中采样的次数的结果小于“区块对齐”,size的值等于“区块对齐”;否则size的值等于“区块对齐” × 一帧音频数据中采样的次数的结果:

if (st->codecpar->block_align > 1) {if (size < st->codecpar->block_align)size = st->codecpar->block_align;size = (size / st->codecpar->block_align) * st->codecpar->block_align;
}

让size的值取上述得到的size值和“该WAV音频文件中还剩下多少个字节没被读取”中的最小值,这是因为读取WAV音频文件到最后,剩下还未被读取的数据的字节数是不满一个packet的大小的:

size = FFMIN(size, left);

最后通过av_get_packet函数(关于该函数用法可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet函数分析》),增加该packet大小至size个字节,也就是让pkt->size增至size字节,从而设置AVPacket结构体中的size成员变量:

ret  = av_get_packet(s->pb, pkt, size);

三、总结

1.区块对齐(每个采样点所需的字节数)是从WAV音频文件的WAV Header中解码出来的。

2.nb_samples为一帧音频数据中采样的次数。如果音频码率大于0,计算音频码率(单位为bits/s) ‌÷ 8 ‌÷ 10 ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,然后求该值是2的多少次幂,这个最终计算的得到结果就是nb_samples。

3.WAV音频文件每个packet的size值一般为:区块对齐 × nb_samples。如果读取到WAV音频文件的最后,size值为剩下的还未被读取的不满一个packet大小的字节数。


http://www.ppmy.cn/embedded/104468.html

相关文章

在Ubuntu上使用apt工具安装RabbitMQ

创建安装脚本 cd home/ madir scripts cd scripts 创建脚本前&#xff0c;需要确认Linux版本。不同的版本对应着不同的运行脚本。 lsb_release -a 查看Linux版本 可以看到&#xff0c;我的Ubuntu版本是22.04。 在这里找到对应的脚本复制。 创建脚本文件&#xff1a; ca…

驱动开发系列17 - PCI总线

一:概述 PCI(外设计算机互连)或PCIe总线是现代计算机的主要组成部分,了解它的工作原理对于理解许多Linux设备驱动程序非常重要。 关于PCI总线本身有很多好的信息(在维基百科和其他地方),而Linux内核中也有关于PCI处理子系统实际实现的文档。然而,这两种现有来源…

单片机中的定时器:精确时间的掌控者

在单片机的世界里&#xff0c;定时器就像是一个精确的时间守护者&#xff0c;默默地为各种任务提供准确的时间基准。从简单的定时功能到复杂的实时控制系统&#xff0c;定时器都发挥着至关重要的作用。本文将深入探讨单片机中的定时器&#xff0c;包括其工作原理、应用场景以及…

python网络爬虫(四)——实战练习

0.为什么要学习网络爬虫 深度学习一般过程:   收集数据&#xff0c;尤其是有标签、高质量的数据是一件昂贵的工作。   爬虫的过程&#xff0c;就是模仿浏览器的行为&#xff0c;往目标站点发送请求&#xff0c;接收服务器的响应数据&#xff0c;提取需要的信息&#xff0c…

前端速通面经八股系列(一)—— CSS篇

CSS高频面经目录 一、CSS基础1. CSS选择器及其优先级2. CSS中可继承与不可继承属性有哪些3. display的属性值及其作用4. display的block、inline和inline-block的区别5. 隐藏元素的方法有哪些6. link和import的区别7. transition和animation的区别8. display:none与visibility:…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 9月2日,星期一

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年9月2日 星期一 农历七月三十 1、 2024年中非合作论坛峰会将于9月4日至6日在北京举行&#xff0c;多国总统抵京。 2、 铁路新规&#xff1a;明确旅客在开车前和开车后当日均可改签预售期内车票。 3、 证监会&#xff1a;在…

组合优化与凸优化 学习笔记2 凸集 凸锥 超平面

凸集定义&#xff1a; 只要线段就可以了&#xff0c;可见要求比仿射集低&#xff0c;仿射集肯定是凸集 凸组合&#xff1a; 和仿射集一样&#xff0c;这两种定义是等价的。 凸包&#xff1a; 锥与凸锥&#xff1a; 可以看到如果锥的开∠大于180小于360那就不是凸集了。 注意锥…

HarmonyOS 鸿蒙获取微信授权和持续获取位置信息

获取授权 PermissionManager.ets import { BusinessError } from "kit.BasicServicesKit"; import { abilityAccessCtrl, bundleManager, PermissionRequestResult, Permissions, common ,Want} from "kit.AbilityKit";/*** 查询是否有单个权限* param pe…