本文針對 C++ 初學者,示範如何使用 Windows Multimedia API (winmm.lib) 實作一個簡易錄音機:設定音訊參數、開啟輸入裝置、雙緩衝採集資料、即時回放,並將錄製內容寫入標準 WAV 檔案。程式碼基於 VC 知識庫第 26 期栾義明先生的《基於 API 的錄音機程式》擴充而來,新增 WAV 檔案儲存功能,適合作為多媒體程式設計入門參考。
typedef struct {
WORD wFormatTag; // 格式標籤 (WAVE_FORMAT_PCM 等)
WORD nChannels; // 聲道數 (1=單聲道, 2=立體聲)
DWORD nSamplesPerSec; // 採樣率 (8k, 11.025k, 22.05k, 44.1k Hz)
DWORD nAvgBytesPerSec; // 平均位元率 = 採樣率 × 區塊對齊
WORD nBlockAlign; // 區塊對齊 = 聲道數 × 位深/8
WORD wBitsPerSample; // 位深 (8 或 16 bit)
WORD cbSize; // 擴充資訊大小
} WAVEFORMATEX;
typedef struct {
LPSTR lpData; // 緩衝區位址
DWORD dwBufferLength; // 緩衝區長度
DWORD dwBytesRecorded; // 已錄製位元組數
DWORD dwUser; // 使用者自訂資料
DWORD dwFlags; // 旗標 (WHDR_DONE, WHDR_PREPARED 等)
DWORD dwLoops; // 迴圈播放次數
struct wavehdr_tag* lpNext; // 保留
DWORD reserved; // 保留
} WAVEHDR;
波形輸入/輸出裝置採用非同步訊息通知,無需輪詢:
| 輸入裝置訊息 | 輸出裝置訊息 | 說明 |
|---|---|---|
| MM_WIM_OPEN | MM_WOM_OPEN | 裝置開啟完成 |
| MM_WIM_DATA | MM_WOM_DATA | 緩衝區滿/播放完畢,需處理資料 |
| MM_WIM_CLOSE | MM_WOM_CLOSE | 裝置關閉完成 |
開發者只需在對應訊息處理函式中填寫邏輯,系統自動觸發回呼。
// 分配兩個輸入緩衝區實現雙緩衡
pBuffer1 = (PBYTE)malloc(INP_BUFFER_SIZE);
pBuffer2 = (PBYTE)malloc(INP_BUFFER_SIZE);
// 分配兩個 WAVEHDR 結構
pWaveHdr1 = (PWAVEHDR)malloc(sizeof(WAVEHDR));
pWaveHdr2 = (PWAVEHDR)malloc(sizeof(WAVEHDR));
// 設定波形格式 (範例: 11.025kHz, 8bit, 單聲道)
WAVEFORMATEX waveform = {0};
waveform.wFormatTag = WAVE_FORMAT_PCM;
waveform.nChannels = 1;
waveform.nSamplesPerSec = 11025;
waveform.wBitsPerSample = 8;
waveform.nBlockAlign = waveform.nChannels * waveform.wBitsPerSample / 8;
waveform.nAvgBytesPerSec = waveform.nSamplesPerSec * waveform.nBlockAlign;
// 開啟輸入裝置
waveInOpen(&hWaveIn, WAVE_MAPPER, &waveform, (DWORD_PTR)hwnd, 0, CALLBACK_WINDOW);
// 準備並加入緩衝區
waveInPrepareHeader(hWaveIn, pWaveHdr1, sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn, pWaveHdr2, sizeof(WAVEHDR));
pWaveHdr1->lpData = (LPSTR)pBuffer1; pWaveHdr1->dwBufferLength = INP_BUFFER_SIZE;
pWaveHdr2->lpData = (LPSTR)pBuffer2; pWaveHdr2->dwBufferLength = INP_BUFFER_SIZE;
waveInAddBuffer(hWaveIn, pWaveHdr1, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, pWaveHdr2, sizeof(WAVEHDR));
// 開始錄音
waveInStart(hWaveIn);
此訊息在緩衝區填滿時觸發,為核心邏輯:
// 將資料追加至儲存緩衝區 pSaveBuffer (需動態擴充)
// ...
// 寫入 WAV 檔案標頭 (RIFF/WAVE/fmt /data chunks)
CFile m_file;
CFileException fe;
CString path = _T("F:\\audio.wav");
m_file.Open(path, CFile::modeCreate | CFile::modeReadWrite, &fe);
DWORD riffSize = dwDataLength + 36; // 檔案總大小 - 8
m_file.Write("RIFF", 4);
m_file.Write(&riffSize, 4);
m_file.Write("WAVE", 4);
// fmt chunk
m_file.Write("fmt ", 4);
DWORD fmtSize = 16; // PCM 固定 16
m_file.Write(&fmtSize, 4);
m_file.Write(&waveform.wFormatTag, 2);
m_file.Write(&waveform.nChannels, 2);
m_file.Write(&waveform.nSamplesPerSec, 4);
m_file.Write(&waveform.nAvgBytesPerSec, 4);
m_file.Write(&waveform.nBlockAlign, 2);
m_file.Write(&waveform.wBitsPerSample, 2);
// data chunk
m_file.Write("data", 4);
m_file.Write(&dwDataLength, 4);
m_file.Write(pSaveBuffer, dwDataLength);
m_file.Close();
WAVEFORMATEX 呼叫 waveOutOpen 開啟輸出裝置WAVEHDR 指向資料緩衝區 (可為剛錄製的記憶體或讀取的檔案)waveOutPrepareHeader / waveOutWrite 送出播放MM_WOM_DATA 中重複送出下一塊或清理waveInAddBuffer 循環佇列模式,避免頻繁 malloc/free。完整原始碼包含對話框介面與訊息映射,可直接編譯執行 (VC6.0 / Win2K 測試通過)。歡迎高手指教:xuejing0103@126.com