三、pcm音频转wav

前言

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


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


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

猜你喜欢

转载自blog.csdn.net/weixin_44092851/article/details/128307024