头图

C++ 中使用 waveInOpen 进行音频录制的详细解析

在C++中进行音频录制时,可以使用 Windows 提供的 Waveform Audio API,其中 waveInOpen 函数是用于启动音频输入设备录音的关键函数。本文将详细介绍如何使用 waveInOpen 进行音频录制,并通过回调函数处理录音数据,同时讲解每一部分代码的作用与原理。

1. 函数概述与基本结构

waveInOpen 是一个用来打开音频输入设备(如麦克风)的函数,它设置录音格式并启动录音设备。其基本结构如下:

MMRESULT waveInOpen(
  PHWAVEIN    phwi,
  UINT        uDeviceID,
  LPWAVEFORMATEX pwfx,
  DWORD_PTR   dwCallback,
  DWORD_PTR   dwInstance,
  DWORD       fdwOpen
);
  • phwi:返回设备句柄,用于后续的录音操作。
  • uDeviceID:指定设备ID,WAVE_MAPPER 表示使用默认输入设备。
  • pwfx:音频格式结构体,定义了录音的参数(采样率、位深、通道数等)。
  • dwCallback:回调函数,当录音缓冲区数据准备好时会被调用。
  • dwInstance:附加的用户数据,通常为0。
  • fdwOpen:打开设备的标志,CALLBACK_FUNCTION 表示使用函数回调。

2. 回调函数 waveInProc

回调函数是处理音频数据的核心。当录音数据准备好时,waveInProc 会被调用处理数据。常见的回调消息是 WIM_DATA,它指示缓冲区内有录音数据可以处理。其函数原型如下:

void CALLBACK waveInProc(
  HWAVEIN hwi,
  UINT uMsg,
  DWORD_PTR dwInstance,
  DWORD_PTR dwParam1,
  DWORD_PTR dwParam2
);
  • uMsg:消息类型(如 WIM_DATA 表示录音数据准备好)。
  • dwParam1:指向 WAVEHDR 结构的指针,包含录音数据的缓冲区。
  • dwParam2:未使用。

回调函数的主要任务是处理录音数据,如保存数据到文件或进行进一步处理。处理完数据后,需要重新准备录音缓冲区以继续录音。

3. 代码解析

以下是基于 waveInOpen 进行音频录制的简单示例代码:

#include <windows.h>
#include <mmsystem.h>

// 回调函数,处理录音数据
void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
    if (uMsg == WIM_DATA) {
        WAVEHDR* pWaveHdr = reinterpret_cast<WAVEHDR*>(dwParam1);
        // 在这里处理录音数据,可以保存到文件或进行其他操作
        // ...

        // 准备好缓冲区,继续录音
        waveInAddBuffer(hwi, pWaveHdr, sizeof(WAVEHDR));
    }
}

int main() {
    HWAVEIN hWaveIn;
    WAVEFORMATEX wfx;
    WAVEHDR whdr;

    // 设置录音格式
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = 1;
    wfx.nSamplesPerSec = 44100;
    wfx.wBitsPerSample = 16;
    wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
    wfx.cbSize = 0;

    // 打开录音设备
    waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, (DWORD_PTR)waveInProc, 0, CALLBACK_FUNCTION);

    // 准备录音缓冲区
    whdr.lpData = new char[1024];
    whdr.dwBufferLength = 1024;
    whdr.dwBytesRecorded = 0;
    whdr.dwUser = 0;
    whdr.dwFlags = 0;
    whdr.dwLoops = 0;

    // 添加录音缓冲区
    waveInPrepareHeader(hWaveIn, &whdr, sizeof(WAVEHDR));
    waveInAddBuffer(hWaveIn, &whdr, sizeof(WAVEHDR));

    // 开始录音
    waveInStart(hWaveIn);

    // 录音时间
    Sleep(5000); // 录制5秒

    // 停止录音
    waveInStop(hWaveIn);

    // 清理资源
    waveInUnprepareHeader(hWaveIn, &whdr, sizeof(WAVEHDR));
    delete[] whdr.lpData;
    waveInClose(hWaveIn);

    return 0;
}

4. 代码分解与工作原理

4.1 设置音频格式

wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 1;                    // 单声道录音
wfx.nSamplesPerSec = 44100;           // 采样率:44.1 kHz
wfx.wBitsPerSample = 16;              // 每个样本16位
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;  // 每块对齐的字节数
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; // 每秒字节数
wfx.cbSize = 0;                       // 未使用

这段代码设置了录音的格式,采用 16位单声道 音频,采样率为44.1kHz,这是常见的音频格式。

4.2 打开音频设备

waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, (DWORD_PTR)waveInProc, 0, CALLBACK_FUNCTION);

使用 waveInOpen 打开默认的音频输入设备,设置录音格式并指定回调函数。当设备准备好音频数据时,会调用 waveInProc

4.3 准备缓冲区并开始录音

whdr.lpData = new char[1024];          // 分配录音数据缓冲区
whdr.dwBufferLength = 1024;            // 缓冲区大小
waveInPrepareHeader(hWaveIn, &whdr, sizeof(WAVEHDR));  // 准备缓冲区
waveInAddBuffer(hWaveIn, &whdr, sizeof(WAVEHDR));     // 添加缓冲区

这里分配了一个1024字节的缓冲区,并准备好缓冲区以供录音使用。每次录音结束后,缓冲区数据将会通过回调函数 waveInProc 进行处理。

4.4 录音和停止录音

waveInStart(hWaveIn);  // 开始录音
Sleep(5000);            // 录制5秒
waveInStop(hWaveIn);   // 停止录音

调用 waveInStart 启动录音,录制5秒后调用 waveInStop 停止录音。

4.5 清理资源

waveInUnprepareHeader(hWaveIn, &whdr, sizeof(WAVEHDR));
delete[] whdr.lpData;
waveInClose(hWaveIn);

录音结束后,需要释放分配的缓冲区,并关闭音频输入设备,释放资源。

5. 总结与注意事项

  1. 错误处理:实际应用中,需要增加错误处理逻辑,以确保设备正确打开并能处理所有可能的错误(如设备不可用或缓冲区处理失败)。
  2. 回调机制:回调函数是处理录音数据的核心,开发者需要确保每次录音后重新准备缓冲区,以实现连续录音。
  3. 性能考虑:尽管上面的示例只录制5秒数据,实际应用中可能需要更高效的缓冲区管理和多线程支持,尤其在录音长时间运行的场景下。

通过以上步骤,你可以使用 Windows API 实现基本的音频录制功能。


蓝易云
33 声望3 粉丝