天天看點

ffmpeg學習 pcm檔案轉wav檔案

前面有詳細介紹PCM檔案資料格式,本文簡單說明PCM檔案如何轉換成WAV檔案,并通過代碼實作轉換過程。

WAV檔案格式

WAV是微軟公司專門為Windows開發的一種标準數字音頻檔案,符合資源互換檔案格式(RIFF)規範,支援多種音頻位數、采樣頻率和聲道。基于PCM編碼的WAV格式的音頻資料無壓縮,能被生聲霸卡直接支援,檔案體積大。

類似視訊封裝格式,WAV檔案可以認為是儲存音頻采樣資料的容器封裝,僅在PCM資料前增加一些wav檔案資訊。基于PCM編碼的标準WAV協定如下圖

ffmpeg學習 pcm檔案轉wav檔案

整個wav檔案隻有一個RIFF塊,預設id為“RIFF”,其chunksize為不包含前兩個字段檔案大小(filesize - 8),format預設為”WAVE”。子塊“fmt ”(注意有一個空字元)描述了音頻采樣資料格式,塊長度固定為16位元組。子塊“data”承載pcm資料。

示例代碼

根據pcm檔案資訊,首先建立WAV檔案格式檔案頭資訊,再讀取pcm資料,之後寫出到檔案即可。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

// PCM WAV Format (44 bytes)
typedef struct WavHeader {
    uint32_t chunk_id;   // "RIFF"
    uint32_t chunk_size; // (44-8) + pcm_data_len
    uint32_t format;     // "WAVE"

    uint32_t sub_chunk_1_id;     // "fmt "
    uint32_t sub_chunk_1_size;   // 16 (PCM)
    uint16_t audio_format;       //  1 - PCM
    uint16_t num_channels;       // 1-mono, 2-stero
    uint32_t sample_rate;
    uint32_t byte_rate;          // sample_rate * block_align
    uint16_t block_align;        // num_channels * bit_per_sample
    uint16_t bit_per_sample;    

    uint32_t sub_chunk_2_id;     // "data"
    uint32_t sub_chunk_2_size;   //  pcm_data_len
    
    uint8_t  data[0];  // data pointer (不占記憶體)
}WavHeader;

int main()
{
    // pcm 資料
    const char *pcm_file_name = "../files/Titanic_8000_s16_stero.pcm";

    FILE *pcm_file = fopen(pcm_file_name,"rb");
    if(!pcm_file) {
         printf("can not read pcm file %s\n", pcm_file_name);
         return 0;
    }

    int32_t pos_s = ftell(pcm_file);
    fseek(pcm_file, 0, SEEK_END);
    int32_t pos_e = ftell(pcm_file);
  
    int32_t pcm_data_len = pos_e - pos_s;

    rewind(pcm_file);

    // wav 資料
    const char *wav_file_name = "Titanic_8000_s16_stero.wav";

    FILE *wav_file = fopen(wav_file_name, "wb");
    if(!wav_file) {
        printf("can not read pcm file %s\n", wav_file_name);
        return 0;
    }

    void *wav_buff = malloc(sizeof(WavHeader) + pcm_data_len);

    WavHeader *wavHeader = (WavHeader*)wav_buff;

    memcpy(&wavHeader->chunk_id,"RIFF",4);
    wavHeader->chunk_size = 36 + pcm_data_len;
    memcpy(&wavHeader->format, "WAVE", 4);

    memcpy(&wavHeader->sub_chunk_1_id, "fmt ", 4);
    wavHeader->sub_chunk_1_size = 16;
    wavHeader->audio_format = 1; 
    wavHeader->num_channels = 2;
    wavHeader->sample_rate = 8000;
    wavHeader->bit_per_sample = 16;
    wavHeader->block_align = wavHeader->num_channels * wavHeader->bit_per_sample / 8;
    wavHeader->byte_rate = wavHeader->sample_rate * wavHeader->block_align;

    memcpy(&wavHeader->sub_chunk_2_id, "data", 4);
    wavHeader->sub_chunk_2_size = pcm_data_len;

    // read pcm data
    fread(wavHeader->data, 1, pcm_data_len, pcm_file);

    // 寫wav頭、PCM資料
    fwrite(wavHeader, 1, sizeof(WavHeader) + pcm_data_len, wav_file);

    // 關閉檔案
    fclose(pcm_file);
    fclose(wav_file);

    free(wav_buff);

    return 0;
}
           

注意WAV檔案結構體中存在一個數組長度為0的uint8_t data[0]成員,該指針data不占用結構體大小,通常這種方用于不定長讀的記憶體配置設定。

程式運作儲存的檔案大小如下圖所示,wav檔案也确實比pcm檔案大了44個位元組。

ffmpeg學習 pcm檔案轉wav檔案

使用MediaInfo檢視wav檔案資訊如下圖,檔案可以正常播放。

ffmpeg學習 pcm檔案轉wav檔案