前面有詳細介紹PCM檔案資料格式,本文簡單說明PCM檔案如何轉換成WAV檔案,并通過代碼實作轉換過程。
WAV檔案格式
WAV是微軟公司專門為Windows開發的一種标準數字音頻檔案,符合資源互換檔案格式(RIFF)規範,支援多種音頻位數、采樣頻率和聲道。基于PCM編碼的WAV格式的音頻資料無壓縮,能被生聲霸卡直接支援,檔案體積大。
類似視訊封裝格式,WAV檔案可以認為是儲存音頻采樣資料的容器封裝,僅在PCM資料前增加一些wav檔案資訊。基于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個位元組。
使用MediaInfo檢視wav檔案資訊如下圖,檔案可以正常播放。