首页 > 其他分享 >10_PCM转WAV

10_PCM转WAV

时间:2022-10-05 13:56:18浏览次数:62  
标签:10 文件 chunk header PCM WAV data

播放器是无法直接播放PCM的,因为播放器并不知道PCM的采样率、声道数、位深度等参数。当PCM转成某种特定的音频文件格式后(比如转成WAV),就能够被播放器识别播放了。

本文通过2种方式(命令行、编程)演示一下:如何将PCM转成WAV。

WAV文件格式

在进行PCM转WAV之前,先再来认识一下WAV的文件格式

  • WAV、AVI文件都是基于RIFF标准的文件格式
  • RIFF(Resource Interchange File Format,资源交换文件格式)由Microsoft和IBM提出
  • 所以WAV、AVI文件的最前面4个字节都是RIFF四个字符

WAV 文件标准格式如下:**
WAV文件格式

我们用一个数据格式使用十六进制展示的大小为 72 字节 WAVE 格式文件举例:
WAV文件格式

下面来一张通俗易懂的图:
WAV文件格式

每一个chunk(数据块)都由3部分组成:

  • id:chunk的标识
  • data size:chunk的数据部分大小,字节为单位
  • data,chunk的数据部分

整个WAV文件是一个RIFF chunk,它的data由3部分组成:

  • format:文件类型
  • fmt chunk
    • 音频参数相关的chunk
    • 它的data里面有采样率、声道数、位深度等参数信息
  • data chunk
    • 音频数据相关的chunk
    • 它的data就是真正的音频数据(比如PCM数据)

RIFF chunk除去data chunk的data(音频数据)后,剩下的内容可以称为:WAV文件头,一般是44字节。

命令行

通过下面的命令可以将PCM转成WAV。

// ffmpeg 输入文件参数 -i 输入文件 输出文件参数 输出文件

ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm out.wav

需要注意的是:上面命令生成的WAV文件头有78字节。对比44字节的文件头,它多增加了一个34字节大小的LIST chunk。

关于LIST chunk的参考资料:

加上一个输出文件参数-bitexact可以去掉LIST Chunk。

ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm -bitexact out2.wav

编程

在PCM数据的前面插入一个44字节的WAV文件头,就可以将PCM转成WAV。

WAV的文件头结构

WAV的文件头结构大概如下所示:

#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;

PCM转WAV核心实现

封装到了FFmpegUtil类的pcm2wav方法中。

#include <QFile>
#include <QDebug>

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

void FFmpegUtil::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)
                            - sizeof (header.riffChunkId)
                            - sizeof (header.riffChunkDataSize);

    // 打开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();
}

调用函数

// 封装WAV的头部
WAVHeader header;
header.numChannels = 2;
header.sampleRate = 44100;
header.bitsPerSample = 16;
header.audioFormat = 1;

// 调用函数
FFmpegUtil::pcm2wav(header, "F:/in.pcm", "F:/out.wav");

代码链接

标签:10,文件,chunk,header,PCM,WAV,data
From: https://www.cnblogs.com/zuojie/p/16755465.html

相关文章

  • 09_使用SDL播放PCM
    通过命令ffpay播放PCM可以使用ffplay播放《08_音频录制02_编程》中录制好的PCM文件,测试一下是否录制成功。播放PCM需要指定相关参数:ar:采样率ac:声道数f:采样格式,sam......
  • 2022.10.5java特性和优势
    Java构建工具:Ant,Maven,Jekins应用服务器:Tomcat,Jettty,Jboss,Websphere,weblogicWeb开发:Struts,Spring,Hibernate,myBatis开发工具:Eclipse,Netbean,intellij......
  • 【分享】从Mybatis源码中,学习到的10种设计模式
    作者:小傅哥​沉淀、分享、成长,让自己和他人都能有所收获!......
  • qt5.2+VS2010(SP1) 中文乱码
    一,用QStringLiteralQMessageBox::about(this,"clicked",QStringLiteral("1按钮被单击1"));二、现在Qt5放弃了上面的方法,网上都建议用:首先将VC里的源代码都保存(VS菜单”文件......
  • 20221004(匈)
    20221004题目来源:George_Plover(乔治魄罗蛙)题目t1两个年轻人思路​ 考虑题目中所说的最优方案是什么。显然,如果只剩一堆,那么将这一堆直接选完就是最优方案。而如......
  • 20221004
    20221004(兄)题目来源:乔治魄罗蛙t1有两个年轻人题目背景有人问我,发生甚么事了?我一看,哦!原来是昨天,有两个年轻人,一个数学考\(150\),一个物理考\(110\),在教室里练题。......
  • 10.4 George_Plover
    题面T1一道数学(博弈论)分析先手搓几个数据,找找规律,除非个数为1,其余的一旦先手先选,那一定先手获胜,反之必败那只用统计1的个数即可,但如果全是1,需要特判#include<cstdi......
  • 【笨方法学python】ex10 - 那是什么
    代码如下:点击查看代码#-*-coding:utf-8--*-#那是什么tabby_cat="\tI'mtabbedin."persian_cat="I'msplit\nonaline."Backslash_cat="I'm\\a\\ca......
  • 代码随想录训练营|Day 15|102, 226, 101
    102.BinaryTreeLevelOrderTraversalGiventhe root ofabinarytree,return thelevelordertraversalofitsnodes'values.(i.e.,fromlefttoright,le......
  • 代码随想录day15 | 102.二叉树的层序遍历 226.反转二叉树 101.对称二叉树
    102.二叉树的层序遍历题目|文章1.迭代思路1.创建一个队列2.确定每一层的节点个数,对每一层进行遍历,将结果输出。实现点击查看代码classSolution{public:ve......