C++ waveOutWrite缓冲区永远不会返回到应用程序

C++ waveOutWrite缓冲区永远不会返回到应用程序,c++,waveout,C++,Waveout,我对Microsoft的WaveOut API有问题: edit1:添加到示例项目的链接: edit2:删除链接,它不代表问题 播放一些音频后,当我想终止给定的播放流时,我调用函数: waveOutClose(hWaveOut_); 但是,即使在调用waveOutClose()之后,有时库仍会访问以前由waveOutWrite()传递给它的内存,从而导致无效的内存访问 然后,在释放缓冲区之前,我尝试确保所有缓冲区都标记为已完成: PcmPlayback::~PcmPlayback() { if

我对Microsoft的WaveOut API有问题:

edit1:添加到示例项目的链接: edit2:删除链接,它不代表问题

播放一些音频后,当我想终止给定的播放流时,我调用函数:

waveOutClose(hWaveOut_);
但是,即使在调用waveOutClose()之后,有时库仍会访问以前由waveOutWrite()传递给它的内存,从而导致无效的内存访问

然后,在释放缓冲区之前,我尝试确保所有缓冲区都标记为已完成:

PcmPlayback::~PcmPlayback()
{
if(hWaveOut_ == nullptr)
    return;

    waveOutReset(hWaveOut_); // infinite-loops, never returns

for(auto it = buffers_.begin(); it != buffers_.end(); ++it)
    waveOutUnprepareHeader(hWaveOut_, &it->wavehdr_, sizeof(WAVEHDR));

while( buffers_.empty() == false ) // infinite loops
    removeCompletedBuffers();

waveOutClose(hWaveOut_);

//Unhandled exception at 0x75629E80 (msvcrt.dll) in app.exe: 
// 0xC0000005: Access violation reading location 0xFEEEFEEE.
}

void PcmPlayback::removeCompletedBuffers()
{
for(auto it = buffers_.begin(); it != buffers_.end();)
{
    if( it->wavehdr_.dwFlags & WHDR_DONE )
    {
        waveOutUnprepareHeader(hWaveOut_, &it->wavehdr_, sizeof(WAVEHDR));
        it = buffers_.erase(it);
    }
    else
        ++it;
}
}
然而,这种情况从未发生过——缓冲区从未变为空。wavehdr_u2;.dwFlags==18将剩下4-5个块(我相信这意味着这些块仍标记为正在播放)

我如何解决这个问题


@Martin Schlott(“您能提供一个循环,在这个循环中您可以将缓冲区写入waveOutWrite吗?”) 这不是一个循环,相反,我有一个函数,每当我通过网络接收到音频数据包时,就会调用该函数:

void PcmPlayback::addData(const std::vector<short> &rhs)
{
removeCompletedBuffers();

if(rhs.empty())
    return;

// add new data
buffers_.push_back(Buffer());

Buffer & buffer = buffers_.back();
buffer.data_ = rhs;
ZeroMemory(&buffers_.back().wavehdr_, sizeof(WAVEHDR));
buffer.wavehdr_.dwBufferLength = buffer.data_.size() * sizeof(short);
buffer.wavehdr_.lpData = (char *)(buffer.data_.data());
waveOutPrepareHeader(hWaveOut_, &buffer.wavehdr_, sizeof(WAVEHDR)); // prepare block for playback
waveOutWrite(hWaveOut_, &buffer.wavehdr_, sizeof(WAVEHDR));
}
void PcmPlayback::addData(const std::vector&rhs)
{
removeCompletedBuffers();
if(rhs.empty())
返回;
//添加新数据
buffers_u.推回(Buffer());
Buffer&Buffer=buffers_.back();
buffer.data=rhs;
零内存(&buffers_.back().wavehdr_,sizeof(wavehdr));
buffer.wavehdr_u2;.dwBufferLength=buffer.data_2;u2;.size()*sizeof(短);
buffer.wavehdr_u2;.lpData=(char*)(buffer.data_2;u2data());
waveOutprepreaheader(hWaveOut_u,&buffer.wavehdr_u,sizeof(wavehdr));//准备播放块
waveOutWrite(hWaveOut_u和buffer.wavehdr_u,sizeof(wavehdr));
}

如果您不调用

waveOutUnprepareHeader
到您使用之前使用的每个缓冲区

waveOutClose
flagfield _dwFlags似乎表示缓冲区仍在排队(WHDR_INQUEUE | WHDR_PREPARED)请尝试:

在未准备缓冲之前

在分析您的代码之后,我发现了两个与waveOut无关的问题/bug(有趣的是,您使用的是C++11,但却是最古老的媒体接口)。使用向量作为缓冲区。在一些调用操作中,向量被复制!我发现的一个bug是:

typedef std::function<void(std::vector<short>)> CALLBACK_FN;
typedef std::函数回调\u FN;
而不是:

typedef std::function<void(std::vector<short>&)> CALLBACK_FN;
typedef std::函数回调\u FN;
这将强制生成向量的副本。 如果您希望将向量主要用作rawbuffer,请尽量避免使用向量。最好使用std::unique_指针作为缓冲区指针

记录器中的回调不受互斥体的监视,也不检查是否已调用析构函数。破坏发生在回调期间(大部分时间),导致异常

对于您的测试程序,在指责waveOut之前,返回并使用原始指针和静态回调。您的代码不错,但第一个bug已经显示,一个小bug将导致不可预测的错误。在std::数组中组织缓冲区时,我会在那里搜索bug。我猜,您无意中复制了整个缓冲区数组,取消了对错误缓冲区的准备


我没有时间深入挖掘,但我想这些就是问题所在。

我最终找到了我的问题,它是由多个bug和死锁造成的。我将记录这里发生的事情,以便人们将来可以从中学习 当我修复了样本中的bug时,我了解到发生了什么:

  • 在~Recorder.cpp中的waveInClose()之前调用waveInStop()
  • 在~PcmPlayback中调用waveOutClose()之前,请等待所有缓冲区都具有WHDR_DONE标志
执行此操作后,样本工作正常,未显示从未标记的WHDR_DONE标志的行为

在我的主程序中,该行为是由以下情况下发生的死锁引起的:

  • 我有一个对象向量,代表我正在流式传输音频的每个对等点
  • 每个对象都拥有一个播放类
  • 此向量受互斥锁保护
记录器回调:

  • 互斥锁
  • 向每个对等方发送音频包
删除对等:

  • 互斥锁
  • ~PcmPlayback
  • 等待标记WHDR_DONE标志
当我移除一个对等对象,锁定互斥锁,记录器回调也试图获取锁时,就会发生死锁

  • 请注意,这种情况经常发生,因为播放缓冲区通常为(~4*20ms),而录音机的节奏为20ms
  • 在~PcmPlayback中,缓冲区永远不会被标记为WHDR_DONE,对WaveOut API的任何调用也永远不会返回,因为WaveOut API正在等待记录器回调完成,而记录器回调又在mutex.lock()上等待,从而导致死锁

能否提供将缓冲区写入waveOutWrite的循环?我添加了一个小项目的链接,演示了该问题,它将运行3秒钟,并在waveOutClose()无法解决问题之前在每个缓冲区上崩溃WaveOutUnpareHeader()。我仍然在app.exe中的0x75629E80(msvcrt.dll)处收到一个访问冲突:未处理异常:0xC0000005:访问冲突读取位置0xFEEEFEEE。Microsoft的文档声明“必须在设备驱动程序使用数据块完成后调用此函数”,我认为这是指设置WHDR_DONE标志时?(这不会发生在~PcmPlayback()中)没错。仅使用“完成”缓冲区取消准备工作。在我在五月份的扩展答案中提到waveOutReset之前,请尝试waveOutReset。检查缓冲区中的“loop”字段。wavehdr\ux。您在设置值之前设置了整个结构吗?@aCuria。我收到了你的样品,但我需要一段时间才能调查。
typedef std::function<void(std::vector<short>&)> CALLBACK_FN;