首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Windows 波形音訊 API 錄音程式設計全解析:從基礎結構到無限長度錄製

     編輯:關於VC++

前言:波形音訊 API 的歷史地位與現況

Windows Multimedia API (winmm.dll) 中的 waveIn*/waveOut* 系列函數,是 Windows 早期音訊輸入輸出的基石。雖然 Windows Vista 引入 Core Audio API (WASAPI) 提供更低延遲、更豐富的功能,但波形 API 因其簡單、穩定、跨版本相容 (Win95~Win11)、無需複雜 COM 介面,至今仍廣泛應用於教學、舊專案維護、嵌入式 Windows、簡易錄音工具等場景。本文系統講解 waveIn 錄音完整流程、核心結構體、三種回呼模式、緩衝區管理與無限長度錄製技巧。

數位音訊基礎知識速覽

  • PCM (Pulse Code Modulation):固定頻率採樣類比波形,量化為數位值。
  • 奈奎斯特定理:採樣率 ≥ 2 × 最高頻率分量。CD 級 44.1kHz 覆蓋人耳 20kHz 上限。
  • 位元深度:8-bit (256 階、動態範圍 ~48dB)、16-bit (65536 階、~96dB,接近人耳極限)。
  • 聲道:單聲道、立體聲 (雙聲道)。
  • 資料率 = 採樣率 × 位元深度/8 × 聲道數。例:44.1kHz/16bit/立體聲 = 176.4 KB/s。

核心結構體

WAVEFORMATEX — 音訊格式描述

typedef struct {
    WORD  wFormatTag;      // 格式標記,WAVE_FORMAT_PCM = 1
    WORD  nChannels;       // 聲道數:1=單聲道, 2=立體聲
    DWORD nSamplesPerSec;  // 採樣率:8000, 11025, 22050, 44100 等
    DWORD nAvgBytesPerSec; // 平均位元率 = nSamplesPerSec * nBlockAlign
    WORD  nBlockAlign;     // 區塊對齊 = nChannels * wBitsPerSample / 8
    WORD  wBitsPerSample;  // 位元深度:8, 16
    WORD  cbSize;          // 擴充資料大小 (PCM 為 0)
} WAVEFORMATEX;

WAVEHDR — 緩衝區標頭

typedef struct {
    LPSTR  lpData;          // 緩衝區記憶體指標
    DWORD  dwBufferLength;  // 緩衝區總長度 (bytes)
    DWORD  dwBytesRecorded; // 實際錄入/播放位元組數
    DWORD  dwUser;          // 使用者自訂資料
    DWORD  dwFlags;         // 旗標:WHDR_DONE, WHDR_PREPARED, WHDR_INQUEUE...
    DWORD  dwLoops;         // 播放迴圈次數
    struct wavehdr_tag* lpNext; // 驅動程式保留
    DWORD  reserved;        // 保留
} WAVEHDR;

關鍵 API 函數總覽

功能錄音 (waveIn*)播放 (waveOut*)
開啟裝置waveInOpenwaveOutOpen
準備標頭waveInPrepareHeaderwaveOutPrepareHeader
加入緩衝區waveInAddBuffer—
寫入/播放—waveOutWrite
開始waveInStart— (Write 即開始)
停止/重設waveInResetwaveOutReset
釋放標頭waveInUnprepareHeaderwaveOutUnprepareHeader
關閉裝置waveInClosewaveOutClose

回呼訊息機制

waveInOpen 的 dwCallback 參數指定處理方式,由 fdwOpen 旗標決定:

  • CALLBACK_WINDOW:傳送訊息至指定 HWND (MM_WIM_*)。
  • CALLBACK_THREAD:張貼執行緒訊息 (PostThreadMessage)。
  • CALLBACK_FUNCTION:直接呼叫回呼函數 (需 __stdcall,不可呼叫非安全 API)。
  • CALLBACK_EVENT:設定事件物件 (Vista+)。

錄音訊息 (MM_WIM_*)

  • MM_WIM_OPEN:裝置開啟完成,可進行初始化。
  • MM_WIM_DATA:緩衝區滿或錄音停止,lParam 指向 WAVEHDR*,核心處理點。
  • MM_WIM_CLOSE:裝置關閉,釋放資源。

播放訊息 (MM_WOM_*):對應 OPEN/DONE/CLOSE。

完整錄音流程

// 1. 定義格式
WAVEFORMATEX wfx = { WAVE_FORMAT_PCM, 1, 22050, 22050, 1, 8, 0 }; // 22.05kHz/8bit/單聲道

// 2. 開啟錄音裝置 (視窗回呼模式)
HWAVEIN hWaveIn;
waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, (DWORD_PTR)hWnd, 0, CALLBACK_WINDOW);

// 3. 分配兩個緩衝區 (雙緩衡輪換)
#define BUF_SIZE 8192
WAVEHDR hdr1 = {0}, hdr2 = {0};
BYTE buf1[BUF_SIZE], buf2[BUF_SIZE];
hdr1.lpData = (LPSTR)buf1; hdr1.dwBufferLength = BUF_SIZE;
hdr2.lpData = (LPSTR)buf2; hdr2.dwBufferLength = BUF_SIZE;
waveInPrepareHeader(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInPrepareHeader(hWaveIn, &hdr2, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &hdr2, sizeof(WAVEHDR));

// 4. 開始錄音
waveInStart(hWaveIn);

// 5. 訊息迴圈處理 MM_WIM_DATA (見下文)

// 6. 停止與清理
waveInReset(hWaveIn);
waveInUnprepareHeader(hWaveIn, &hdr1, sizeof(WAVEHDR));
waveInUnprepareHeader(hWaveIn, &hdr2, sizeof(WAVEHDR));
waveInClose(hWaveIn);

無限長度錄製核心:MM_WIM_DATA 處理

當緩衝區填滿,驅動程式發送 MM_WIM_DATA,lParam 指向已填滿的 WAVEHDR。此時必須:

  1. 將緩衝區資料寫入檔案/傳送網路/轉存大記憶體緩衝區。
  2. 重新 waveInAddBuffer 同一個 WAVEHDR (資料已處理完畢),實現緩衝區循環使用。

MFC 視窗類別訊息映射範例


BEGIN_MESSAGE_MAP(CRecordWnd, CWnd)
    ON_MESSAGE(MM_WIM_OPEN, OnWimOpen)
    ON_MESSAGE(MM_WIM_DATA, OnWimData)
    ON_MESSAGE(MM_WIM_CLOSE, OnWimClose)
END_MESSAGE_MAP()

LRESULT CRecordWnd::OnWimData(WPARAM wParam, LPARAM lParam) {
    WAVEHDR* pHdr = (WAVEHDR*)lParam;
    if (!pHdr || !(pHdr->dwFlags & WHDR_DONE)) return 0;

    // 1. 寫入檔案 (假設 m_file 已開啲)
    m_file.Write(pHdr->lpData, pHdr->dwBytesRecorded);
    m_dwTotalBytes += pHdr->dwBytesRecorded;

    // 2. 重新加入佇列 (循環緩衝)
    waveInAddBuffer(m_hWaveIn, pHdr, sizeof(WAVEHDR));
    return 0;
}

執行緒模式 (CWinThread) 範例


class CRecordThread : public CWinThread {
    DECLARE_DYNCREATE(CRecordThread)
    HWAVEIN m_hWaveIn;
    CFile m_file;
    WAVEHDR m_hdr[2];
    BYTE* m_buf[2];
public:
    BOOL InitInstance() {
        // 開啟裝置,指定執行緒 ID 回呼
        waveInOpen(&m_hWaveIn, WAVE_MAPPER, &wfx, m_nThreadID, 0, CALLBACK_THREAD);
        // 準備緩衝區...
        return TRUE;
    }
    // 訊息映射
    ON_THREAD_MESSAGE(MM_WIM_DATA, OnWimData)
    ON_THREAD_MESSAGE(MM_WIM_CLOSE, OnWimClose)
    // 處理函數同視窗模式
};

WAV 檔案寫入:標準 RIFF/WAVE 格式


void WriteWaveHeader(CFile& file, const WAVEFORMATEX& wfx, DWORD dwDataSize) {
    DWORD dwRiffSize = 36 + dwDataSize; // 4(RIFF)+4(size)+4(WAVE)+4(fmt )+24(fmt data)+4(data)+4(size)
    file.Write("RIFF", 4);
    file.Write(&dwRiffSize, 4);
    file.Write("WAVE", 4);
    file.Write("fmt ", 4);
    DWORD dwFmtSize = 16; // PCM
    file.Write(&dwFmtSize, 4);
    file.Write(&wfx, sizeof(WAVEFORMATEX)); // 18 bytes, 含 cbSize=0
    file.Write("data", 4);
    file.Write(&dwDataSize, 4);
}
// 錄音結束時呼叫,寫入標頭並更新 RIFF/data 區塊大小

注意:WAVEFORMATEX 為 18 位元組 (含 cbSize),標準 PCM fmt 區塊為 16 位元組 (不含 cbSize),寫入時需手動控制寫入長度或構造 16 位元組結構。

常見問題與避坑指南

問題原因解決
錄音雜音/斷續緩衝區太小、CPU 佔用高導致緩衝區下溢增大緩衝區 (如 16KB~64KB)、提高執行緒優先級、使用雙/三緩衡
無法開啟裝置 (MMSYSERR_ALLOCATED)裝置被其他程式佔用檢查獨佔模式、關閉其他錄音軟體、嘗試 WAVE_MAPPER 自動選擇
錄音速度不正確格式結構體填寫錯誤 (nAvgBytesPerSec、nBlockAlign)嚴格按公式計算:nBlockAlign = nChannels * wBitsPerSample / 8; nAvgBytesPerSec = nSamplesPerSec * nBlockAlign
回呼函數崩潰CALLBACK_FUNCTION 中呼叫非安全 API (如 new、MessageBox、檔案 I/O)改用 CALLBACK_WINDOW/THREAD,或回呼中僅設旗標/PostMessage 通知主執行緒處理
WAV 檔案播放長度錯誤RIFF/data 區塊大小未正確寫入錄音結束前計算總位元組數,SeekToBegin 回寫標頭大小欄位

進階主題:多裝置選擇與格式枚舉

  • waveInGetNumDevs() 取得裝置數。
  • waveInGetDevCaps(uDeviceID, &caps, sizeof(caps)) 取得裝置名稱、支援格式 (caps.dwFormats 位元旗標)。
  • waveInMessage(hWaveIn, DRV_QUERYFUNCTIONINSTANCEID, ...) 進階控制。

結語

波形音訊 API 雖屬舊世代,但其「開啟→準備緩衝→加入佇列→啟動→訊息循環處理→清理」的清晰流程,構成了理解現代音訊管線 (WASAPI、Audio Graph) 的最佳入門模型。掌握雙緩衡循環、MM_WIM_DATA 即時寫入、WAV 標頭構建等核心技巧,即可開發出穩定、低延遲、無長度限制的錄音應用。若專案需超低延遲 (<10ms)、裝置獨佔、多通道同步、音訊端點切換通知等進階功能,請遷移至 WASAPI (共享/獨佔模式) 或 Audio Client API。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved