使用waveIn和waveOut在windows中进行音频录制和实时播放

Posted

技术标签:

【中文标题】使用waveIn和waveOut在windows中进行音频录制和实时播放【英文标题】:audio recording and real-time playing in windows using waveIn and waveOut 【发布时间】:2016-04-19 05:03:13 【问题描述】:

我想录制麦克风音频输入,并以一些小的时间延迟立即播放录制的声音。这将使用缓冲区队列连续完成。

我让代码运行到几乎连续播放麦克风音频输入的程度,但是使用waveOut 在整个音频输出中存在非常短但仍然明显的重复暂停。是什么导致这些烦人的停顿?以及如何删除它们?

另一个问题是,我没有使用任何类似互斥锁的东西,我依赖于 waveIn 和 waveOut 具有相同的采样率和相同的数据量这一事实,因此希望 waveOut 始终遵循 waveIn 而 waveIn 不会写入正在播放的缓冲区。这会是个问题吗?

这是代码,它应该可以编译并运行。我只是让代码运行起来,还远远没有写好。任何关于改进代码的评论都非常受欢迎。

    #include "stdafx.h"
    #include <Windows.h>
    #pragma comment(lib, "winmm.lib")
    #include <iostream>
    #include <fstream>
    #include <sstream>

    using namespace std;
    HANDLE hEvent_BufferReady;
    HANDLE hEvent_FinishedPlaying;
    #define Samplerate 44100
    #define nSec  1

    int _iBuf;
    int _iplaying;
    unsigned long result;


    HWAVEIN hWaveIn;
    HWAVEOUT hWaveOut;
    WAVEFORMATEX pFormat;

    enum  NUM_BUF = 3 ;
    WAVEHDR _header [NUM_BUF];

    DWORD WINAPI RecordingWaitingThread(LPVOID ivalue)
    
        while(1)
        
        WaitForSingleObject(hEvent_BufferReady,INFINITE);


        result = waveInUnprepareHeader (hWaveIn, &_header[_iBuf], sizeof (WAVEHDR));
        _iplaying = _iBuf;
        result = waveOutPrepareHeader(hWaveOut, &_header[_iBuf], sizeof(WAVEHDR));
        result = waveOutWrite(hWaveOut, &_header[_iBuf], sizeof(WAVEHDR));   // play audio
        ++_iBuf;
        if (_iBuf == NUM_BUF)   _iBuf = 0;
        result = waveInPrepareHeader(hWaveIn, & _header[_iBuf], sizeof(WAVEHDR));
        result = waveInAddBuffer (hWaveIn, & _header[_iBuf], sizeof (WAVEHDR)); 
        
        return 0;
    

    DWORD WINAPI PlayingWaitingThread(LPVOID ivalue)
    
        while(1)
            WaitForSingleObject(hEvent_FinishedPlaying,INFINITE);
            waveOutUnprepareHeader(hWaveOut, &_header[_iplaying], sizeof(WAVEHDR));
        
    

    static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1,DWORD dwParam2 )
    
    if(uMsg != WOM_DONE)
    return;
    SetEvent(hEvent_FinishedPlaying);
    


    void CALLBACK myWaveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
    
    if(uMsg != WIM_DATA)
    return;
    SetEvent(hEvent_BufferReady);
    

    int main(int argc, _TCHAR* argv[])
    

        hEvent_BufferReady=CreateEvent(NULL,FALSE, FALSE, NULL);
        hEvent_FinishedPlaying = CreateEvent(NULL,FALSE, FALSE, NULL);


        pFormat.wFormatTag = WAVE_FORMAT_PCM; // simple, uncompressed format
        pFormat.nChannels = 1; // 1=mono, 2=stereo
        pFormat.nSamplesPerSec = Samplerate; 
        pFormat.wBitsPerSample = 16; // 16 for high quality, 8 for telephone-grade
        pFormat.nBlockAlign = pFormat.nChannels*pFormat.wBitsPerSample/8; 
        pFormat.nAvgBytesPerSec = (pFormat.nSamplesPerSec)*(pFormat.nChannels)*(pFormat.wBitsPerSample)/8; 
        pFormat.cbSize=0;


        short int  *_pBuf;
        size_t bpbuff =4000;//= (pFormat.nSamplesPerSec) * (pFormat.nChannels) * (pFormat.wBitsPerSample)/8;
        _pBuf = new short int [bpbuff * NUM_BUF];

        result = waveInOpen(&hWaveIn, WAVE_MAPPER,&pFormat, (DWORD)myWaveInProc, 0L, CALLBACK_FUNCTION);
        result = waveOutOpen(&hWaveOut, WAVE_MAPPER, &pFormat, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION);
        // initialize all headers in the queue
        for ( int i = 0; i < NUM_BUF; i++ )
        
            _header[i].lpData = (LPSTR)&_pBuf [i * bpbuff];
            _header[i].dwBufferLength = bpbuff*sizeof(*_pBuf);
            _header[i].dwFlags = 0L;
            _header[i].dwLoops = 0L;
        

        DWORD myThreadID;
        DWORD myThreadIDPlay;
        HANDLE hThread;
        HANDLE hThreadPlay;
        hThread = CreateThread(NULL, 0, RecordingWaitingThread,NULL,0,&myThreadID);
        hThreadPlay = CreateThread(NULL, 0, PlayingWaitingThread,NULL,0,&myThreadIDPlay);

        _iBuf = 0;

        waveInPrepareHeader(hWaveIn, & _header[_iBuf], sizeof(WAVEHDR));
        waveInAddBuffer (hWaveIn, & _header[_iBuf], sizeof (WAVEHDR));

        waveInStart(hWaveIn);

        getchar();
        waveInClose(hWaveIn);
        waveOutClose(hWaveOut);
        CloseHandle(hThread);
        CloseHandle(hThreadPlay);

        CloseHandle(hEvent_BufferReady);
        CloseHandle(hEvent_FinishedPlaying);

        return 0;
    

【问题讨论】:

我只使用了带有回调线程的 wavIn/wavOut 函数——即我给它一个线程并处理 mm 系统发送给我的 WOM_DONE 消息。快速浏览一下,我注意到的差异包括 (a) 我使用了至少 4 个缓冲区,而您似乎只使用了 3 个。我填充了所有缓冲区,然后使用 WavOutWrite 将它们全部清除。然后,随着每个缓冲区完成,下一个缓冲区开始,我收到一条消息,告诉我缓冲区已播放。然后我合成歌曲的下一部分(大约 1/8 秒,或 5292 个样本) 并再次使用 WavOutWrite 将其爆破,直到接下来的 3 个缓冲区播放完毕。这只是确保 MM 子系统有足够的数据和缓冲区来处理的问题(从内存中)。这种方法似乎比使用一个足够大的缓冲区来处理整个几分钟的性能要好 miles。它还可以让您以很小的延迟暂停播放(当然,比我检测到的延迟要快)当缓冲区长度不适合我正在做的合成时,我确实有糟糕的播放,但应该与这个 Q 无关。 试图增加缓冲区的数量,但没有任何区别。接下来我想尝试将 waveOut 延迟至少两个缓冲区,因为 waveIn 正在填充缓冲区queue,将这批buffer一起爆破到wavOut设备,然后每次收到WOM_DONE报文,如果已经填充了新的buffer,则将新的buffer写入waveOut设备,否则等待新的buffer填满。 这听起来是个不错的策略。很可能从录制完成到播放开始,您需要处理几个缓冲区。实际上,我刚刚找到了我上次回复时正在查看的代码的旧版本 - 我现在正在查看的代码只有两个正在使用的缓冲区。我看不出它会有什么不同,但它的价值是什么——我正在播放的缓冲区保存 32 位浮点样本。我也没有使用 WaitForSingleObject 或 SetEvent 。我会尝试您的代码,但很久以前就卸载了麦克风驱动程序。 :oops: 我会从播放代码开始,使用预先生成的数据(如正弦波)来查看故障发生在输出端还是录制端。 【参考方案1】:

您的程序的问题是您声明的bpbuff 太小。只需尝试使用size_t bpbuff = 4410; 声明它,您将摆脱音频流中所有这些重复的中断。

顺便说一句,我认为您可以摆脱这些线程方法,使您的代码更加简单,如下所示:

#include "stdafx.h"
#include <Windows.h>
#pragma comment(lib, "winmm.lib")
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;
#define Samplerate 44100

static HWAVEIN hWaveIn;
static HWAVEOUT hWaveOut;

enum  NUM_BUF = 3 ;
WAVEHDR _header [NUM_BUF];

void CALLBACK myWaveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)

    static int _iBuf;
    waveOutWrite(hWaveOut, &_header[_iBuf], sizeof(WAVEHDR));   // play audio
    ++_iBuf;
    if (_iBuf == NUM_BUF)   _iBuf = 0;
    waveInAddBuffer (hWaveIn, & _header[_iBuf], sizeof (WAVEHDR)); 


int main(int argc, _TCHAR* argv[])

    WAVEFORMATEX pFormat;
    pFormat.wFormatTag = WAVE_FORMAT_PCM; // simple, uncompressed format
    pFormat.nChannels = 1; // 1=mono, 2=stereo
    pFormat.nSamplesPerSec = Samplerate; 
    pFormat.wBitsPerSample = 16; // 16 for high quality, 8 for telephone-grade
    pFormat.nBlockAlign = pFormat.nChannels*pFormat.wBitsPerSample/8; 
    pFormat.nAvgBytesPerSec = (pFormat.nSamplesPerSec)*(pFormat.nChannels)*(pFormat.wBitsPerSample)/8; 
    pFormat.cbSize=0;

    short int  *_pBuf;
    size_t bpbuff = 4410;//= (pFormat.nSamplesPerSec) * (pFormat.nChannels) * (pFormat.wBitsPerSample)/8;
    _pBuf = new short int [bpbuff * NUM_BUF];

    waveInOpen(&hWaveIn, WAVE_MAPPER,&pFormat, (DWORD)myWaveInProc, 0L, CALLBACK_FUNCTION);
    waveOutOpen(&hWaveOut, WAVE_MAPPER, &pFormat, (DWORD_PTR)nullptr, 0, CALLBACK_FUNCTION);
    // initialize all headers in the queue
    for ( int i = 0; i < NUM_BUF; i++ )
    
        _header[i].lpData = (LPSTR)&_pBuf [i * bpbuff];
        _header[i].dwBufferLength = bpbuff*sizeof(*_pBuf);
        _header[i].dwFlags = 0L;
        _header[i].dwLoops = 0L;
        waveInPrepareHeader(hWaveIn, & _header[i], sizeof(WAVEHDR));
    
    waveInAddBuffer (hWaveIn, & _header[0], sizeof (WAVEHDR));

    waveInStart(hWaveIn);

    getchar();
    waveInClose(hWaveIn);
    waveOutClose(hWaveOut);
    delete _pBuf;

    return 0;

这段代码用更少的代码执行完全相同的任务。干杯,伙计!

【讨论】:

如何获取实际(原始)数据并写入文件?我可以在 myWavInProc 中执行此操作吗?像这样:outfile

以上是关于使用waveIn和waveOut在windows中进行音频录制和实时播放的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 WaveOut\waveIn Api 录制和播放声音 [关闭]

记录来自 NAudio WaveIn 的输入,并输出到 NAudio WaveOut

波形音频缓冲区

音频处理 音频输出

[C#] NAudio 各种常见使用方式 播放 录制 转码 音频可视化

WinMM 库问题