【问题标题】:waveOutWrite buffers are never returned to applicationwaveOutWrite 缓冲区永远不会返回给应用程序
【发布时间】:2013-10-29 17:10:36
【问题描述】:

我对 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_.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));
}

【问题讨论】:

  • 能否提供将缓冲区写入 waveOutWrite 的循环?
  • 我添加了一个演示问题的小项目的链接,它将运行 3 秒并崩溃

标签: c++ waveout


【解决方案1】:

如果您不调用,可能会发生所描述的行为

waveOutUnprepareHeader

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

waveOutClose

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

waveOutReset

在取消准备缓冲区之前。

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

typedef std::function<void(std::vector<short>)> CALLBACK_FN;

代替:

typedef std::function<void(std::vector<short>&)> CALLBACK_FN;

强制复制向量。 如果您希望将其主要用作原始缓冲区,请尽量避免使用向量。最好使用 std::unique_pointer 作为缓冲区指针。

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

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

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

【讨论】:

  • 在 waveOutClose() 之前对每个缓冲区调用 waveOutUnprepareHeader() 并不能解决问题。我仍然遇到访问冲突:app.exe 中 0x75629E80 (msvcrt.dll) 处的未处理异常:0xC0000005:访问冲突读取位置 0xFEEEFEEE。
  • Microsoft 的文档指出“必须在设备驱动程序完成数据块后调用此函数”,我认为这是指设置 WHDR_DONE 标志时的意思吗? (这在 ~PcmPlayback() 中不会发生)
  • 没错。 Unpreparing 仅适用于“完成”缓冲区。在我在可能扩展的答案中提到它之前尝试 waveOutReset。
  • 检查 buffer.wavehdr_ 中的“循环”字段。在设置值之前,您是否对整个结构进行了归零?
  • @aCuria。我得到了你的样本,但我需要一段时间才能查看它。
【解决方案2】:

我最终找到了我的问题,它是由多个错误和死锁引起的。我将记录这里发生的事情,以便人们将来可以从中学习 当我修复示例中的错误时,我被告知发生了什么:

  • 在 ~Recorder.cpp 中的 waveInClose() 之前调用 waveInStop()
  • 在 ~PcmPlayback 中调用 waveOutClose() 之前,等待所有缓冲区都具有 WHDR_DONE 标志。

执行此操作后,示例工作正常并且没有显示 WHDR_DONE 标志的行为从未被标记。

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

  • 我有一个对象向量,代表我正在与之流式传输音频的每个对等方
  • 每个对象都有一个播放类
  • 此向量受互斥体保护

录音机回调:

  • mutex.lock()
  • 向每个对等方发送音频数据包。

删除对等体:

  • mutex.lock()
  • ~Pcm回放
  • 等待标记 WHDR_DONE 标志

当我删除一个对等点时发生死锁,锁定互斥体并且记录器回调也尝试获取锁。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多