WAV是一種以RIFF為基礎的無壓縮音頻編碼格式,該格式以Header、Format Chunk及Data Chunk三部分構成,
本文簡要決議了各部分的構成要素,概述了如何使用C++對檔案頭進行決議以及提取音頻資料,

上圖展示了WAV檔案格式,包括每一field的大小與端序
Header
- ChunkID: 4位元組大端序,檔案從此處開始,對于WAV或AVI檔案,其值總為“RIFF”,
- ChunkSize: 4位元組小端序,表示檔案總位元組數減8,減去的8位元組表示ChunkID與ChunkSize本身所占位元組數,
- Format: 4位元組大端序,對于WAV檔案,其值總為“WAVE”
Format Chunk
- Subchunk1ID: 4位元組大端序,其值總為“fmt ”,表示Format Chunk從此處開始,
- Subchunk1Size: 4位元組小端序,表示Format Chunk的總位元組數減8,
- AudioFormat: 2位元組小端序,對于WAV檔案,其值總為1,
- NumChannels: 2位元組小端序,表示總聲道個數,
- SampleRate: 4位元組小端序,表示在每個通道上每秒包含多少幀,
- ByteRate: 4位元組小端序,大小等于SampleRate * BlockAlign,表示每秒共包含多少位元組,
- BlockAlign: 2位元組小端序,大小等于NumChannels * BitsPerSample / 8, 表示每幀的多通道總位元組數,
- BitsPerSample: 2位元組小端序,表示每幀包含多少位元,
Data Chunk
- Subchunk2ID: 4位元組大端序,其值總為“data”,表示Data Chunk從此處開始,
- Subchunk2Size: 4位元組小端序,表示data的總位元組數,
- data: 小端序,表示音頻波形的幀資料,各聲道按幀交叉排列,
使用C++決議WAV檔案
檔案頭結構
定義結構體WaveHeader來保存WAV檔案頭,即Header、Format Chunk及Data Chunk的非data部分,此外在該結構體中添加了num_frame欄位,用來保存檔案總幀數,由于Header、Format Chunk與Data Chunk之間可能有其他說明資訊,所以還添加了start_pos欄位用來保存真正的data開始的位置,
typedef struct WaveHeader {
char chunk_id[4] = { 0 };
unsigned int chunk_size = 0;
char format[4] = { 0 };
char fmt_chunk_id[4] = { 0 };
unsigned int fmt_chunk_size = 0;
unsigned short audio_fomat = 0;
unsigned short num_channels = 0;
unsigned int sample_rate = 0;
unsigned int byte_rate = 0;
unsigned short block_align = 0;
unsigned short bits_per_sample = 0;
char data_chunk_id[4] = { 0 };
unsigned int data_chunk_size = 0;
int num_frame = 0;
int start_pos = 0;
};
提取檔案頭
/*
* fname: 檔案路徑
* wh: 用來保存檔案頭的結構體實體
*/
void getHead(string fname, WaveHeader &wh) {
/*
*由于事先并不知道檔案大小,故定義足量大小的char陣列覆寫檔案頭
*之后可根據提取到的ChunkSize來定義提取音頻資料用的陣列
*/
const int HEAD_LENGTH = 256 * 1024;//256kb
char buf[HEAD_LENGTH];
FILE *stream;
freopen_s(&stream, fname.c_str(), "rb", stderr);
fread(buf, 1, HEAD_LENGTH, stream);
//記錄檔案讀取位置
int pos = 0;
//尋找“RIFF”標記
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'R'&&buf[pos + 1] == 'I'&&buf[pos + 2] == 'F'&buf[pos + 3] == 'F') {
wh.chunk_id[0] = 'R';
wh.chunk_id[1] = 'I';
wh.chunk_id[2] = 'F';
wh.chunk_id[3] = 'F';
pos += 4;
break;
}
++pos;
}
//讀取Header部分
wh.chunk_size = *(int *)&buf[pos];
pos += 4;
wh.format[0] = buf[pos];
wh.format[1] = buf[pos + 1];
wh.format[2] = buf[pos + 2];
wh.format[3] = buf[pos + 3];
pos += 4;
//尋找“fmt”標記
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'f'&&buf[pos + 1] == 'm'&&buf[pos + 2] == 't') {
wh.fmt_chunk_id[0] = 'f';
wh.fmt_chunk_id[1] = 'm';
wh.fmt_chunk_id[2] = 't';
pos += 4;
break;
}
++pos;
}
//讀取Format Chunk部分
wh.fmt_chunk_size = *(int *)&buf[pos];
pos += 4;
wh.audio_fomat = *(short *)&buf[pos];
pos += 2;
wh.num_channels = *(short *)&buf[pos];
pos += 2;
wh.sample_rate = *(int *)&buf[pos];
pos += 4;
wh.byte_rate = *(int *)&buf[pos];
pos += 4;
wh.block_align = *(short *)&buf[pos];
pos += 2;
wh.bits_per_sample = *(short *)&buf[pos];
pos += 2;
//尋找“data”標記
while (pos < HEAD_LENGTH) {
if (buf[pos] == 'd'&&buf[pos + 1] == 'a'&&buf[pos + 2] == 't'&buf[pos + 3] == 'a') {
wh.data_chunk_id[0] = 'd';
wh.data_chunk_id[1] = 'a';
wh.data_chunk_id[2] = 't';
wh.data_chunk_id[3] = 'a';
pos += 4;
break;
}
++pos;
}
//讀取Data Chunk的非data部分
wh.data_chunk_size = *(int *)&buf[pos];
pos += 4;
//記錄真正音頻資料的開始位置
wh.start_pos = pos;
//計算檔案總幀數
wh.num_frame = wh.data_chunk_size / (wh.num_channels*(wh.bits_per_sample / 8));
}
提取波形資料(data)
/*
* fname: 檔案路徑
* wh: 對應的檔案頭結構體實體
*/
void getData(string fname, WaveHeader &wh){
//記錄檔案讀取位置
int pos = wh.start_pos;
//為加快處理速度,根據ChunkSize將檔案一次讀入記憶體
FILE *stream;
freopen_s(&stream, fname.c_str(), "rb", stderr);
char* file_data = https://www.cnblogs.com/zillyrex/p/new char[wh.chunk_size + 8];
fread(file_data, 1, wh.chunk_size + 8, stream);
//以每幀2位元組為例
short left_data;
short right_data;
while(pos < wh.start_pos + wh.data_chunk_size){
left_data = *(short*)&file_data[pos];
//TODO: 處理左聲道資料
pos += 2;
right_data = *(short*)&file_data[pos];
//TODO: 處理右聲道資料
pos += 2;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/131549.html
標籤:其他
