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

news2024/12/23 23:35:38

一、引言

从文章《音视频入门基础: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);

 

 

 

三、总结

WAV音频文件每个packet的size值一般为:“区块对齐(每个采样点所需的字节数)” × 一帧音频数据中采样的次数。如果读取到WAV音频文件的最后,size值为剩下的还未被读取的不满一个packet大小的字节数。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2085714.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware16安装包+详细安装教程

VMware Workstation Pro16.0安装 安装包下载&#xff1a; 通过百度网盘分享的文件&#xff1a;VMware16.0.rar 链接&#xff1a;https://pan.baidu.com/s/1ZSWns5kJYUmhpZFjuKXqrQ?pwdv7s2 提取码&#xff1a;v7s2右键解压之后的安装包【VMware-workstation-full-16.0.0-16…

FrameNet介绍——从同义词语义知识库到框架语义知识库

FrameNet 是一个为期三年的项目&#xff0c;获得了 NSF&#xff08;美国国家科学基金会&#xff09;的支持&#xff0c;专注于基于语料库的计算词典编纂。 项目特点 FrameNet承诺使用语料库证据&#xff08;corpus evidence&#xff09;来进行语义和句法的概括&#xff1b; 并…

网络基础-实现在Windows系统下的socket环境地址通信

实现客户端和服务端的数据交互 1.写所要实现功能的声明&#xff08;封装在tcpsocket.h文件&#xff09; #ifndef TCPSOCKET_H #define TCPSOCKET_H//在Windows下进行网络编程&#xff0c;需要引入Windows的socket库 #include <winsock2.h> //做一些预编译工作&#xff…

MyBatis结果集复杂映射超详细版(一对多关系映射)

目录 1.一对多关系映射 1.1创建两个表&#xff1a;goods表与goods_class表 1.2xml文件中两部分&#xff1a;与(存放SQL语句)1.3数据库中&#xff1a;测试SQL语句&#xff0c;涉及到的知识点&#xff1a;左连接 1.一对多关系映射 1.1创建两个表&#xff1a;goods表与goods_c…

C++对C的扩充(8.28)

1.使用C手动封装一个顺序表&#xff0c;包括成员数组1个&#xff0c;成员变量n个 代码&#xff1a; #include <iostream>using namespace std;//类型重命名 using datatype int; #define MAX 30struct seqList { private: //私有权限datatype *data; //相当于 …

【项目源码】终于有人将打字游戏和编程英语结合起来啦!编程初学者的福音

Hello&#xff01;各位彦祖&#xff0c;亦菲们&#xff01;又是美好的一天&#xff01;今天给大家分享一个Java项目源码&#xff1a;Java打字游戏项目源码&#xff01; 看到这里&#xff0c;你可能会说&#xff01; 一个破打字游戏有什么可神气的&#xff01;&#xff01;&…

【自由能系列(中级)】状态与动作的协同机制解析 ——从马尔可夫毯到大脑功能的全方位剖析

状态与动作的协同机制解析 ——从马尔可夫毯到大脑功能的全方位剖析 Synergistic Mechanism of States and Actions —— A Comprehensive Analysis from Markov Blanket to Brain Function 核心结论&#xff1a; 中文总结&#xff1a; 系统将状态划分为内部状态和隐藏或外…

Flutter中的Key

在Flutter 中&#xff0c;Key 是 几乎所有 widget 都具有的属性。为什么 widget 具有 Key 呢&#xff1f;Key的作用是什么&#xff1f; 什么是 Key Key是Widget、Element 和 SemanticNodes 的标识符。 Key 是Widget、Element 和 SemanticNodes的唯一标识。例如对于 Widget 在 …

MyBatis的学习————下篇

目录 一、动态SQL 简介 1、if标签 2、where标签 3、trim标签 4、choose、when、otherwise 5、foreach 5.1、批量删除 5.2、批量添加 6、sql标签 二、MyBatis的缓存 1、一级缓存 2、二级缓存 3、二级缓存的相关配置 4、MyBatis缓存查询的顺序 5、 第三方缓存EHCac…

如何在Windows 11上关闭无响应的应用程序?这里有详细步骤

序言 无响应的应用程序令人沮丧,但更糟糕的是这些应用程序拒绝关闭。如果你发现自己处于这种情况,我们有几种方法可以帮助你强制关闭Windows 11 PC上的这些应用程序。让我们找出可用的解决方案。 使用键盘快捷键结束程序 关闭无响应应用程序的最简单方法是使用Windows键盘…

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛-task2

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛 YOLO(You Only Look Once)上分心得分享 YOLO(You Only Look Once) YOLO算的上是近几年最火的目标检测模型了&#xff0c;被广泛的应用在工业、学术等领域。 YOLOv1&#xff08;You Only Look Once 第一版&#xff09;于 2…

基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设

麒麟信安操作系统&#xff0c;作为行业数智化建设的安全根基&#xff0c;为电力业务系统提供了稳定可靠的底层平台&#xff0c;在全球能源结构转型大潮中扮演着至关重要的角色。某光伏电站项目中&#xff0c;基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设&…

c#如何加密exe程序防止反编译附软件

1. 先说软件&#xff0c;使用的软件是Dotfuscator&#xff0c;下载地址如下&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/6f2e785c003f2. 软件使用方法&#xff0c;打开软件&#xff0c;选择Create New Project 3. 找到input&#xff0c;把你需要加密的文件导入 4.…

k8s项目的发布

目录 三种发布方式 1.蓝绿发布 2.金丝雀发布&#xff08;灰度发布&#xff09; 实验&#xff1a;k8s实现金丝雀发布 3.滚动发布&#xff08;默认形式&#xff09; 因为应用升级以及新旧业务切换&#xff0c;所以在这个过程当中如何保证对外的服务正常是一个非常重要的问题…

手把手教你如何使用Python连接MySQL数据

数据库编程是在应用程序中与数据库交互和管理数据的关键部分。MySQL是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;在Python中进行MySQL数据库编程相对容易。 本文介绍如何使用Python进行MySQL数据库编程&#xff0c;包括连接数据库、执行SQL查询…

高频面试题:SpringMVC的执行流程

SpringMVC一直以来都是面试中的重点&#xff0c;尽管随着近年来springboot和微服务的广泛流行&#xff0c;关于对springMVC的考察比重略有下降&#xff0c;但依然是面试中的重点&#xff0c;也需要我们对其有一个比较清楚和全面的认识。 如果将java的发展史中重要的组件进行排…

备忘录模式 详解

备忘录模式 简介: 保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象, 允许在不破坏封装性的前提下&#xff0c;捕获和恢复对象的内部状态。 场景: 很多地方都用到了备忘录模式, 比如网络消息的序列化和反序列化, 数据的本地保存与加载等, 最简单的json的dump和loa…

全能与专精:探索AI模型的未来之路

AI模型&#xff1a;追求全能还是专精&#xff1f; 近日&#xff0c;OpenAI预计在秋季推出代号为“草莓”的新AI。从专注于数学问题到处理主观营销策略&#xff0c;"草莓"模型展现出惊人的多样性。而这种全能型 AI 是否代表了未来趋势&#xff1f;相比专攻于某一领域…

OpenAI 将于今年秋天推出新的先进“Strawberry草莓”生成式人工智能产品

今年秋季&#xff0c;OpenAI将推出一款备受瞩目的新型生成式人工智能产品——“草莓”。据悉&#xff0c;这款名为“草莓”的AI程序将带来一系列创新突破&#xff0c;它将大幅提升人工智能处理复杂数学题、执行战略任务以及深入探索各种主题的能力&#xff0c;而这一切无需依赖…

前景堪忧?SaaS巨头Salesforce,25年辉煌后能否继续领跑市场?

最近&#xff0c;时常听到有人说Salesforce失去了活力&#xff0c;这或许是对整个生态系统的普遍感受。多年来&#xff0c;Salesforce一直保持着巨大的发展势头&#xff0c;通过收购、创新和建立良好的合作伙伴关系已发展成为云计算行业巨头。在经历了近25年创纪录的增长和创新…