三、pcm音频转wav

news2024/11/25 14:34:40

前言

ffmpeg录制下来的音频为pcm格式(内部存储着十六进制数据),但pcm格式的音频无法直接播放

本文先将pcm转换成wav格式(提要提前了解音频知识)


首先分析wav文件格式(wav的本质是在pcm数据前加上文件头),即在pcm的十六进制数据前加上文件头(文件头也是十六进制数据,但有些内容是固定的,有些内容是变化的)

image-20220620110050438

pcm转换成wav基本思路:

首先封装一个方法,该方法需要实现在传入wav文件头后把源pcm文件转为wav文件。具体功能是先将文件头的十六进制数据写入文件(需要记录下变化的地方,等待读取pcm的数据之后才能确定),然后将pcm中的十六进制数据写入wav文件。这些思路都是有wav文件格式确定的

难点:

文件头到底怎么写?(以下是wav文件头的格式,第二张图为文件头十六进制存储的样子,一个十六进制为一个字节,一个ASCII编码占一个字节,文件头总长为44字节)

注意:这里细节的地方太多了,无法每一处都提及

image-20220620110942728

image-20220620111007075

通过结构的方式来理清文件头

typedef struct {
    // RIFF chunk的id
    uint8_t riffChunkId[4] = {'R', 'I', 'F', 'F'};
    // RIFF chunk的data大小,即文件总长度减去8字节
    uint32_t riffChunkDataSize;

    // "WAVE"
    uint8_t format[4] = {'W', 'A', 'V', 'E'};

    /* fmt chunk */
    // fmt chunk的id
    uint8_t fmtChunkId[4] = {'f', 'm', 't', ' '};
    // fmt chunk的data大小:存储PCM数据时,是16
    uint32_t fmtChunkDataSize = 16;
    // 音频编码,1表示PCM,3表示Floating Point
    uint16_t audioFormat = AUDIO_FORMAT_PCM;
    // 声道数
    uint16_t numChannels;
    // 采样率
    uint32_t sampleRate;
    // 字节率 = sampleRate * blockAlign
    uint32_t byteRate;
    // 一个样本的字节数 = bitsPerSample * numChannels >> 3
    uint16_t blockAlign;
    // 位深度
    uint16_t bitsPerSample;

    /* data chunk */
    // data chunk的id
    uint8_t dataChunkId[4] = {'d', 'a', 't', 'a'};
    // data chunk的data大小:音频数据的总长度,即文件总长度减去文件头的长度(一般是44)
    uint32_t dataChunkDataSize;
} WAVHeader;

直接把结构体写入文件的方式很巧妙,既确定了写入顺序,又记录下了哪些是会变化的地方

注意:结构体中的某些值是根据音频相关数据决定的,而非一成不变。那相关的值到底如何填,可以直接去搜wav的头文件或者自行下载一个wav文件,将其拖入qt中查看其中的头文件中的数据

image-20220620144728523

比如audioFormat可能填1可能填3,需要找到相应的含义,按情况而定

image-20220620144923864

具体代码:

FFmpegs.h

#ifndef FFMPEGS_H
#define FFMPEGS_H

#include <stdint.h>

#define AUDIO_FORMAT_PCM 1
#define AUDIO_FORMAT_FLOAT 3

// WAV文件头(44字节)
typedef struct {
    // RIFF chunk的id
    uint8_t riffChunkId[4] = {'R', 'I', 'F', 'F'};
    // RIFF chunk的data大小,即文件总长度减去8字节
    uint32_t riffChunkDataSize;

    // "WAVE"
    uint8_t format[4] = {'W', 'A', 'V', 'E'};

    /* fmt chunk */
    // fmt chunk的id
    uint8_t fmtChunkId[4] = {'f', 'm', 't', ' '};
    // fmt chunk的data大小:存储PCM数据时,是16
    uint32_t fmtChunkDataSize = 16;
    // 音频编码,1表示PCM,3表示Floating Point
    uint16_t audioFormat = AUDIO_FORMAT_PCM;
    // 声道数
    uint16_t numChannels;
    // 采样率
    uint32_t sampleRate;
    // 字节率 = sampleRate * blockAlign
    uint32_t byteRate;
    // 一个样本的字节数 = bitsPerSample * numChannels >> 3
    uint16_t blockAlign;
    // 位深度
    uint16_t bitsPerSample;

    /* data chunk */
    // data chunk的id
    uint8_t dataChunkId[4] = {'d', 'a', 't', 'a'};
    // data chunk的data大小:音频数据的总长度,即文件总长度减去文件头的长度(一般是44)
    uint32_t dataChunkDataSize;
} WAVHeader;

class FFmpegs
{
public:
    FFmpegs();
    static void pcm2wav(WAVHeader &header,const char *pcmFilename,const char *wavFilename);
};


#endif // FFMPEGS_H

FFmpegs.cpp

#include "ffmpegs.h"

#include <QFile>
#include <QDebug>

FFmpegs::FFmpegs()
{
    
}

void FFmpegs::pcm2wav(WAVHeader &header, const char *pcmFilename, const char *wavFilename)
{
    header.blockAlign = header.bitsPerSample * header.numChannels >> 3;
    header.byteRate = header.sampleRate * header.blockAlign;
    
    // 打开pcm文件
    QFile pcmFile(pcmFilename);
    if (!pcmFile.open(QFile::ReadOnly)) {
        qDebug() << "文件打开失败" << pcmFilename;
        return;
    }
    header.dataChunkDataSize = pcmFile.size();
    header.riffChunkDataSize = header.dataChunkDataSize
            + sizeof (WAVHeader) - 8;
    
    // 打开wav文件
    QFile wavFile(wavFilename);
    if (!wavFile.open(QFile::WriteOnly)) {
        qDebug() << "文件打开失败" << wavFilename;
        
        pcmFile.close();
        return;
    }
    
    // 写入头部
    wavFile.write((const char *) &header, sizeof (WAVHeader));
    
    // 写入pcm数据
    char buf[1024];
    int size;
    while ((size = pcmFile.read(buf, sizeof (buf))) > 0) {
        wavFile.write(buf, size);
    }
    
    // 关闭文件
    pcmFile.close();
    wavFile.close();
}

封装的代码中也可以看出,基本上头文件中的数据都已经初始化了,还剩下声道数numChannels、采样率sampleRate、位深度bitsPerSample来自行赋值

调用封装的函数:

void MainWindow::on_pushButton_pcm_to_wav_clicked()
{
    // 封装WAV的头部,此处我所有的值是大多数情况下音频都是这些数据
    WAVHeader header;
    header.numChannels = 2;
    header.sampleRate = 44100;
    header.bitsPerSample = 16;

    // 调用函数
    FFmpegs::pcm2wav(header, "E:/media/out.pcm", "E:/media/out.wav");
    QFile file("E:/media/out.wav");
    if(file.exists()){
        qDebug()<<"文件转换成功,wav文件已生成";
    }
}

pcm和wav文件大小对比,wav多了44字节的文件头

image-20220620113859519


注意:本文为个人记录,新手照搬可能会出现各种问题,请谨慎使用


码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

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

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

相关文章

[附源码]Node.js计算机毕业设计电子工厂进销存管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

《Linux运维总结:Centos7.6使用yum安装Mysql8.0.31》

一、使用yum安装mysql服务 官方&#xff1a;下载对应的yum源 1、yum源下载 [rootlocalhost ~]# wget https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm [rootlocalhost ~]# rpm -ivh mysql80-community-release-el7-7.noarch.rpm如下图所示&#xff1a;…

vsftpd 升级3.0.2-29 和 增加账号访问

一、下载文件至/opt 百度网盘&#xff1a; 链接: https://pan.baidu.com/s/1Ad20bm5fvw8WvtaV3uHGYA 提取码: 2pa9 二、安装 2.1、切换目录 命令&#xff1a;cd /opt 2.2、授权 命令&#xff1a;chmod x centos7.6-vsftpd-upgrade.sh 2.3、安装 命令&#xff1a;./cent…

D. Lucky Chains(gcd + 线性筛)

Problem - D - Codeforces 如果一对正整数(x,y)的最大公除数等于1(gcd(x,y)1)&#xff0c;我们就把它们命名为幸运。 让我们把由(x,y)引起的链定义为一个由(x,y), (x1,y1), (x2,y2), ..., (xk,yk)组成的序列&#xff0c;对于某个整数k≥0。 如果链中的所有配对都是幸运的&…

2022-12-13 note

1、linux内核的特点 1. linux内核是完全开源的作者&#xff1a;linusgit --> 代码版本管理工具 2. linux内核源码支持多种不同的架构&#xff0c;比如arm架构&#xff0c;powerPC,mips,Risc-V,X86等 3. linux内核采用模块化的编译的思想 4. 在linux内核中只允许出现C代码或…

优秀的PM如何轻松应对公司90%以上的沟通难题

项目经理和PMO工作中最多的事情往往是沟通&#xff0c;但是你在工作中会不会遇到很多沟通难题&#xff1f;如果其他环节的人处理不好还可以理解&#xff0c;但是咱们处理不好就说不过去了&#xff0c;并且还会让人感觉不专业&#xff0c;丧失很多机会&#xff0c;甚至失去领导的…

代码随想录Day49|121.买卖股票的最佳时期、122.买卖股票的最佳时期II

文章目录121.买卖股票的最佳时期、122.买卖股票的最佳时期II121.买卖股票的最佳时期、 文章讲解&#xff1a;代码随想录 (programmercarl.com) 题目链接&#xff1a;121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定一个数组 prices…

Metal每日分享,调整灰度系数gamma滤镜效果

本案例的目的是理解如何用Metal实现灰度系数效果滤镜&#xff0c;输入像素rgb进行次方运算获取到新的rgb&#xff1b; Demo HarbethDemo地址 实操代码 // 灰度系数滤镜 let filter C7Gamma.init(gamma: 3.0)// 方案1: ImageView.image try? BoxxIO(element: originImage,…

[附源码]Node.js计算机毕业设计儿童闲置物品交易网站Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

六、JavaScript——变量的内存结构

一、内存 定义&#xff1a;内存是用于存储数据的地方&#xff0c;程序要执行一段代码&#xff0c;要先从硬盘加载到内存当中&#xff0c;再由内存发送给CPU,CUP才能对代码进行执行。 注&#xff1a;变量并不存储任何值&#xff0c;而是存储值的内存地址 JS定义一个变量 <s…

Telerik模拟生成规则的新设置

Telerik模拟生成规则的新设置 添加了对Microsoft.NET 7官方版本的支持。 添加了控制模拟生成规则的设置。 Telerik JustLock是一个易于使用的模拟工具&#xff0c;它将帮助您以前所未有的速度、更高的质量编写单元测试。JustLock允许您轻松地将测试场景与难以配置的依赖关系(如…

5G现场网的数字孪生体系架构

现场网面向钢铁生产和统一管理的场景&#xff0c;实现现场设备与数据采集器和控制器的网络连接和数据互通&#xff0c;现场网的ZigBee、蓝牙等短距离技术以及窄带物联网&#xff08; narrowbandinternetofthings,NB-IoT&#xff09;、超远距离&#xff08;longrange,LoRa&#…

病历开发SDK:TX Text Control ActiveX 31.x Crack

Visual Basic 6 应用程序的文档处功能齐全的文档编辑器 TX Text Control ActiveX是一种完全可编程的 Rich Edit 控件&#xff0c;它在专为 Visual Studio 设计的可重用组件中为开发人员提供了广泛的文字处理功能。它提供全面的文本格式、强大的邮件合并功能和所有文字处理关键概…

算法训练四十八天 | LeetCode 198、213、337打家劫舍专题

LeetCode 198 打家劫舍 题目简析&#xff1a; 不能偷相邻的屋子&#xff0c;求能偷到的最大金额 思路分析&#xff1a; //由于是相邻的才偷&#xff0c;因此&#xff0c;我们对于最基础的三间 //dp[3] 应该是 Math.max(dp[2],dp[1]nums[3]) //如果第一间加偷第三间的价值大…

pytest + yaml 框架 -10.allure 生成报告

前言 本插件是基于pytest框架开发的&#xff0c;所以pytest 的插件都能使用&#xff0c;生成报告可以用到 allure 报告 pip 安装插件 pip install pytest-yaml-yoyoallure 报告功能在 v1.0.8 版本上实现 allure 命令行工具 allure 是一个命令行工具&#xff0c;需要去githu…

大话设计模式之设计原则

设计原则 参考链接&#xff1a;https://datawhalechina.github.io/sweetalk-design-pattern/#/content/design_principles/dependence_inversion_principle 主要介绍以下五个设计原则&#xff1a; 1、单一职责原则 顾名思义&#xff0c;一个类只有一个职责&#xff0c;只会…

React 16.8+生命周期(新)

React16之后有三个生命周期被废弃&#xff1a; componentWillMountcomponentWillReceivePropscomponentWillUpdate React 16.8的生命周期分为三个阶段&#xff0c;分别是挂载阶段、更新阶段、卸载阶段。 挂载阶段&#xff1a; constructor&#xff1a;构造函数&#xff0c;最…

【元胞自动机】基于元胞自动机模拟考虑心理策略的人员疏散附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

2022-12-13 工作记录--Vue/JS-音乐的播放与关闭

Vue/JS-音乐的播放与关闭 一、音频资源 音频资源的位置如下&#xff1a;&#x1f447;&#x1f3fb; 二、代码 App.vue 注意点&#xff1a; 1、若切换了音频资源&#xff0c;则需要在 播放音乐前 重新加载资源——见代码里的 myAuto?.load()&#xff1b;2、若切换了页面&…

论文学习——秦淮河水文水动力模型及实时校正

文章目录摘要0 引言1 模型构建1.1 流域概况及资料收集1.2 河道一维水流模拟1.3 边界条件处理1.3.1 流量上边界计算1.3.2 水文下边界处理1.4 节点堰闸流量计算2 实时校正方法2.1 KNN 法2.2 反馈法3 模拟及校正结果4 结论作者&#xff1a;孙文宇、姚成、刘志雨 期刊&#xff1a;《…