音视频之PCM转WAV(八)

前引

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

WAV文件格式

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

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

数据构成

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

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

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

  • format: 文件类型
  • fmt chunk
    • 音频数据相关的chunk
    • 它的data就是真正的音频数据(比如PCM数据)

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

WAV44字节具体构成

代码实现

.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', 'F'};
    /* 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 pcmToWav(WAVHeader &header, const char *pcmFilename, const char *wavFilename);
};

#endif // FFMPEGS_H

.CPP

#include "ffmpegs.h"
#include <QFile>
#include <QDebug>

FFmpegs::FFmpegs()
{

}

void FFmpegs::pcmToWav(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));

    // 打开wav文件
    char buf[1024];
    int size;
    while ((size = pcmFile.read(buf, sizeof(buf))) > 0) {
        wavFile.write(buf, size);
    }

    // 关闭文件
    pcmFile.close();
    wavFile.close();
}

 

posted @ 2021-11-05 17:28  木子沉雨  阅读(500)  评论(0编辑  收藏  举报