WAV (Waveform Audio File Format) 採用 RIFF 容器結構,由多個 Chunk 組成:
RIFF Header (12 bytes)
"RIFF" (4) | FileSize-8 (4) | "WAVE" (4)
fmt Chunk (24 bytes for PCM)
"fmt " (4) | ChunkSize(4)=16 | AudioFormat(2) | Channels(2)
SampleRate(4) | ByteRate(4) | BlockAlign(2) | BitsPerSample(2)
data Chunk
"data" (4) | DataSize (4) | 波形資料...
public struct WavHeader
{
// RIFF Header
public string RiffId; // "RIFF"
public int FileSize; // 總檔案大小 - 8
public string WaveId; // "WAVE"
// fmt Chunk
public string FmtId; // "fmt "
public int FmtSize; // 16 for PCM
public short AudioFormat; // 1 = PCM
public short Channels; // 1=單聲道, 2=立體聲
public int SampleRate; // 8000, 44100, 48000...
public int ByteRate; // SampleRate * Channels * BitsPerSample/8
public short BlockAlign; // Channels * BitsPerSample/8
public short BitsPerSample; // 8, 16, 24, 32
// data Chunk
public string DataId; // "data"
public int DataSize; // 波形資料大小
}
public static WavHeader ReadWavHeader(string filePath)
{
var header = new WavHeader();
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var br = new BinaryReader(fs))
{
// RIFF Header
header.RiffId = new string(br.ReadChars(4));
if (header.RiffId != "RIFF") throw new InvalidDataException("非 RIFF 檔案");
header.FileSize = br.ReadInt32();
header.WaveId = new string(br.ReadChars(4));
if (header.WaveId != "WAVE") throw new InvalidDataException("非 WAVE 格式");
// 尋找 fmt chunk (可能中間有其他 chunk)
while (fs.Position < fs.Length)
{
string chunkId = new string(br.ReadChars(4));
int chunkSize = br.ReadInt32();
if (chunkId == "fmt ")
{
header.FmtId = chunkId;
header.FmtSize = chunkSize;
header.AudioFormat = br.ReadInt16();
header.Channels = br.ReadInt16();
header.SampleRate = br.ReadInt32();
header.ByteRate = br.ReadInt32();
header.BlockAlign = br.ReadInt16();
header.BitsPerSample = br.ReadInt16();
// 若 fmt size > 16,略過擴充資料
if (chunkSize > 16) fs.Seek(chunkSize - 16, SeekOrigin.Current);
}
else if (chunkId == "data")
{
header.DataId = chunkId;
header.DataSize = chunkSize;
break; // 找到 data chunk,後續即為波形資料
}
else
{
// 略過未知 chunk
fs.Seek(chunkSize, SeekOrigin.Current);
}
}
}
return header;
}
| 技術 | 說明 |
|---|---|
| BinaryReader | 直接讀取基本型別 (ReadInt32, ReadInt16, ReadChars),自動處理 Little-Endian |
| Chunk 遍歷 | RIFF 允許任意順序 chunk,必須迴圈尋找 "fmt " 與 "data" |
| 位置控制 | fs.Seek 跳過未知/擴充 chunk |
| 驗證 | 檢查 "RIFF"、"WAVE"、"fmt "、"data" 四個標識 |