音频采集 via Waveform API

Waveform API 采集音频

Waveform API 从 Windows 3.0 时代就登上历史舞台了,至今依然可以运行在最新的 OS 上,不得不佩服 Windows 的兼容性。不过新的 waveform API 是基于 Core Audio 架构之上的,其实就是一个 wrapper。

Waveform API 的优势就是非常简单,兼容性好,不依赖于任何框架,虽然通常我们都使用它录制成 Wav 格式的文件(缺点是非常占用空间),但实际上也可以和其他框架(如 FFmpeg,DShow,MF 等)配合使用编码成压缩的音频格式(如 mp3,aac 等)。

采集流程

waveform api

采集代码

以下是整个 Waveform API 采集过程的概要代码,略去各个函数的具体实现和错误处理:

m_wavInHelper.enumDevices(this, _fillDevices);
m_wavInHelper.selectDevice(nSel);
hr = m_wavInHelper.getSupportedFormats(supportedFormats);
if (SUCCEEDED(hr)) { // add supported formats to a combobox
    if (WAVE_FORMAT_1M08 == (supportedFormats & WAVE_FORMAT_1M08))
        pFormats->SetItemData(pFormats->AddString(_T("11.025 kHz, mono, 8-bit")), WAVE_FORMAT_1M08);
    … …
}
m_wavInHelper.selectDevice(nSel); 
m_wavInHelper.openDevice(samplesPerSec, channels, bitsPerSample);
m_wavInHelper.startRecording(szRecFile);
… … // capturing 
m_wavInHelper.stopRecording();
m_wavInHelper.closeDevice();

CWaveInHelper::enumDevices 函数

枚举到的每一个设备经由回调函数返回设备名称,然后填充到界面上的列表控件中(下图中的 Input ComboBox),每个设备支持的格式也可以通过 API waveInGetDevCaps 枚举出来。
Wav cap

HRESULT CWaveInHelper::enumDevices(void* pContext, PFENUM_DEV_CALLBACK pCallback)
{
    RETURN_IF_NULL(pCallback);

    UINT nDevices = ::waveInGetNumDevs();
    for (UINT i = 0; i < nDevices; ++i) {
        WAVEINCAPS devCaps = { 0 };
        MMRESULT mRes = waveInGetDevCaps(i, &devCaps, sizeof(WAVEINCAPS));
        if (mRes == MMSYSERR_NOERROR)
            pCallback(pContext, devCaps.szPname);
    }
    
    return S_OK;    
}

HRESULT CWaveInHelper::getSupportedFormats(DWORD& supportFormats)
{
    WAVEINCAPS devCaps = { 0 };
    ZeroMemory(&devCaps, sizeof(WAVEINCAPS));
    MMRESULT mRes = ::waveInGetDevCaps(m_deviceID, &devCaps, sizeof(WAVEINCAPS));
    RETURN_IF_FALSE_EX(mRes == MMSYSERR_NOERROR, HRESULT_FROM_WIN32(mRes));

    supportFormats = devCaps.dwFormats;
    return S_OK;
}

CWaveInHelper::openDevice 函数

waveInOpen 指定了回调方式,即在某个时间片的音频数据被录完后,Windows 将通过这个回调来激活对这些数据的处理过程,支持的类型包括 EVENT, FUNCTION, THREAD 和 WINDOW,下面使用的是 FUNCTION 回调,waveInOpen 的第四个参数 _waveInProc 即回调函数。

HRESULT CWaveInHelper::openDevice(DWORD samplesPerSec, DWORD channels, DWORD bitsPerSample)
{
    if (NULL != m_hWaveIn)
        closeDevice();

    m_wavFormat.cbSize = sizeof(WAVEFORMATEX);
    m_wavFormat.nSamplesPerSec = samplesPerSec;
    m_wavFormat.nChannels = (WORD)channels;
    m_wavFormat.wBitsPerSample = (WORD)bitsPerSample;
    m_wavFormat.wFormatTag = WAVE_FORMAT_PCM;
    m_wavFormat.nBlockAlign = m_wavFormat.nChannels * m_wavFormat.wBitsPerSample / 8;
    m_wavFormat.nAvgBytesPerSec = m_wavFormat.nSamplesPerSec * m_wavFormat.nBlockAlign;
    MMRESULT mRes = ::waveInOpen(&m_hWaveIn, m_deviceID, &m_wavFormat, 
                                 (DWORD_PTR)_waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
    RETURN_IF_FALSE_EX(mRes == MMSYSERR_NOERROR, HRESULT_FROM_WIN32(mRes));

    return S_OK;
}

CWaveInHelper::startRecording 函数

waveInPrepareHeaderwaveInAddBuffer 用来添加接收音频数据的缓存,一般至少需要准备两块缓存。因为录音不能间断,当一块填满时没有时间等待你去送入下一块缓存,所以必须提前就准备好。

HRESULT CWaveInHelper::startRecording(LPCTSTR recFilePath)
{
    HRESULT hr = E_FAIL;
    MMRESULT mRes = 0;

    hr = m_wavFile.create(recFilePath, m_wavFormat);
    RETURN_IF_FAILED(hr);

    hr = _prepareBuffers();
    GOTO_LABEL_IF_FAILED(hr, OnErr);

    mRes = ::waveInStart(m_hWaveIn);
    GOTO_LABEL_IF_FALSE_EX(mRes == MMSYSERR_NOERROR, HRESULT_FROM_WIN32(mRes), OnErr);

    m_recording = true;
    return S_OK;
OnErr:
    _unPrepareBuffers();
    return hr;
}

CWaveInHelper::_waveInProc 回调函数

当送入的缓存被录满后,Windows 就会通过指定的方式进行回调,在回调中我们把语音数据写到文件中,然后将下一个缓存添加进去。考虑到这个处理要一定时间,所以要预先加入若干个缓存。

注意:不要在回调函数中调用各种系统 API,容易造成死锁,具体请参考 msdn

Applications should not call any system-defined functions from inside a callback function, except for EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, and timeSetEvent. Calling other wave functions will cause deadlock.

void CALLBACK CWaveInHelper::_waveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    switch(uMsg) {
    case WIM_CLOSE:
        break;
    case WIM_DATA:
        CWaveInHelper*pThis = (CWaveInHelper*)dwInstance;
        pThis->_processInput((WAVEHDR*)dwParam1);
        break;
    case WIM_OPEN:
        break;
    default:
        break;
    }
}

HRESULT CWaveInHelper::_processInput( WAVEHDR* pHdr )
{
    if (WHDR_DONE == (WHDR_DONE & pHdr->dwFlags)) {
        m_wavFile.append(pHdr->lpData, pHdr->dwBytesRecorded);
        if (m_recording) {
            MMRESULT mRes = waveInAddBuffer(m_hWaveIn, pHdr, sizeof(WAVEHDR));
            RETURN_IF_FALSE_EX(mRes == MMSYSERR_NOERROR, HRESULT_FROM_WIN32(mRes));
        }
    }
}

CWaveInHelper::stopRecording 函数

waveInReset 可以清掉尚在等待录音的缓冲区,并通过回调函数返回给程序,注意在停止录音后不要在回调函数中再添加缓存,否则会造成死锁。

HRESULT CWaveInHelper::stopRecording()
{
    HRESULT hr = E_FAIL;
    MMRESULT mRes = 0;
    m_recording = false;

    mRes = waveInStop(m_hWaveIn);
    RETURN_IF_FALSE_EX(mRes == MMSYSERR_NOERROR, HRESULT_FROM_WIN32(mRes));

    mRes = waveInReset(m_hWaveIn);
    PRINT_ERROR_LOG_IF_FALSE(mRes == MMSYSERR_NOERROR, mRes);

    hr = _unPrepareBuffers();
    RETURN_IF_FAILED(hr);

    m_wavFile.close();
    return S_OK;
}

其他框架下的采集

请参考对应的文章。

Blueware
EOF

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值