【秒懂音視頻開發】10_PCM轉WAV


播放器是無法直接播放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文件格式

每一個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 -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核心實現

封裝到了FFmpegs類的pcm2wav函數中。

#include <QFile>
#include <QDebug>

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

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

調用函數

// 封裝WAV的頭部
WAVHeader header;
header.numChannels = 2;
header.sampleRate = 44100;
header.bitsPerSample = 16;

// 調用函數
FFmpegs::pcm2wav(header, "F:/in.pcm", "F:/out.wav");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM