当前位置: 首页 > news >正文

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

一、引言

从文章《音视频入门基础: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大小的字节数。

相关文章:

  • 【硬件知识】从零开始认识GPU
  • 【QT】学习笔记:导出资源中静态文件
  • JMeter 工具安装以及简单使用
  • 【网络安全】Bingbot索引投毒实现储存型XSS
  • 公司一般注册什么样的域名后缀?
  • 仿论坛项目--开发社区首页
  • Opencv中的直方图(1)计算反向投影直方图函数calcBackProject()的使用
  • 洛谷8.30
  • 盲盒小程序开发,探索市场发展优势
  • 基于 OpenCV 的数字图像处理实验平台设计
  • 自己开发完整项目一、登录功能-05(动态权限控制)
  • 创建型设计模式-原型模式(prototype)- python实现
  • 微软AD替代方案统一管理Windows和信创电脑的登录认证与网络准入认证
  • ARM体系与架构
  • C++AVL树
  • 《剑指offer》分解让复杂问题更简单
  • 【剑指offer】让抽象问题具体化
  • JavaScript DOM 10 - 滚动
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • js
  • log4j2输出到kafka
  • springboot_database项目介绍
  • SQLServer插入数据
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 初识MongoDB分片
  • 从零开始学习部署
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 离散点最小(凸)包围边界查找
  • 目录与文件属性:编写ls
  • 配置 PM2 实现代码自动发布
  • 算法---两个栈实现一个队列
  • 新手搭建网站的主要流程
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 昨天1024程序员节,我故意写了个死循环~
  • ​1:1公有云能力整体输出,腾讯云“七剑”下云端
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • (160)时序收敛--->(10)时序收敛十
  • (55)MOS管专题--->(10)MOS管的封装
  • (k8s中)docker netty OOM问题记录
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (四)stm32之通信协议
  • (贪心) LeetCode 45. 跳跃游戏 II
  • .NET 表达式计算:Expression Evaluator
  • .net 后台导出excel ,word
  • .NET 中让 Task 支持带超时的异步等待
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .NET程序员迈向卓越的必由之路
  • @RequestMapping处理请求异常
  • @SpringBootApplication 包含的三个注解及其含义
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)
  • [100天算法】-x 的平方根(day 61)