Pergunta

I have a problem with Microsoft's WaveOut API:

edit1: Added Link to sample project: edit2: removed link, its not representative of the issue

After playing some audio, when I want to terminate a given playback stream, I call the function:

waveOutClose(hWaveOut_);

However, even after waveOutClose() is called, sometimes the library will still access memory previously passed to it by waveOutWrite(), causing an invalid memory access.

I then tried to ensure all the buffers are marked as done before freeing the buffer:

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;
}
}

However, this situation never happens - the buffer never becomes empty. There will be 4-5 blocks remaining with wavehdr_.dwFlags == 18 (I believe this means the blocks are still marked as in playback)

How can I resolve this issue?

@ Martin Schlott ("Can you provide the loop where you write the buffer to waveOutWrite?") Its not quite a loop, instead I have a function that is called whenever I receive an audio packet over the network:

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));
}
Foi útil?

Solução

The described behavior can happen if you do not call

waveOutUnprepareHeader

to every buffer you used before you use

waveOutClose

The flagfield _dwFlags seems to indicate that the buffers are still enqueued (WHDR_INQUEUE | WHDR_PREPARED) try:

waveOutReset

before unprepare buffers.

After analyses your code, I found two problems/bugs which are not related to waveOut (funny, you use C++11 but the oldest media interface). You use a vector as buffer. During some calling operations, the vector is copied! One bug I found is:

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

instead of:

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

which forces a copy of the vector. Try to avoid using vectors if you expect to use it mostly as rawbuffer. Better use std::unique_pointer as buffer pointer.

Your callback in the recorder is not monitored by a mutex, nor does it check if a destructor was already called. The destructing happens during the callback (mostly) which leads to an exception.

For your test program, go back and use raw pointer and static callbacks before blaming waveOut. Your code is not bad, but the first bug already shows, that a small bug will lead to unpredictical errors. As you also organize your buffers in a std::array, I would search for bugs there. I guess, you make a unintentional copy of your whole buffer array, unpreparing the wrong buffers.

I did not have the time to dig deeper, but I guess those are the problems.

Outras dicas

I managed to find my problem in the end, it was caused by multiple bugs and a deadlock. I will document what happened here so people can learn from this in the future I was clued in to what was happening when I fixed the bugs in the sample:

  • call waveInStop() before waveInClose() in ~Recorder.cpp
  • wait for all buffers to have the WHDR_DONE flag before calling waveOutClose() in ~PcmPlayback.

After doing this, the sample worked fine and did not display the behavior of the WHDR_DONE flag never being marked.

In my main program, that behavior was caused by a deadlock that occurs in the following situation:

  • I have a vector of objects representing each peer I am streaming audio with
  • Each Object owns a Playback class
  • This vector is protected by a mutex

Recorder callback:

  • mutex.lock()
  • send audio packet to each peer.

Remove Peer:

  • mutex.lock()
  • ~PcmPlayback
  • wait for WHDR_DONE flags to be marked

A deadlock occurs when I remove a peer, locking the mutex and the recorder callback tries to acquire a lock too.

  • Note that this will happen often because the playback buffer is usually (~4 * 20ms) while the recorder has a cadence of 20ms.
  • In ~PcmPlayback, the buffers will never be marked as WHDR_DONE and any calls to the WaveOut API will never return because the WaveOut API is waiting for the Recorder callback to complete, which is in turn waiting on mutex.lock(), causing a deadlock.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top