Буферы waveOutWrite никогда не возвращаются приложению

У меня проблема с 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;
}
}

Однако такая ситуация никогда не происходит — буфер никогда не становится пустым. Будет 4-5 блоков, оставшихся с wavehdr_.dwFlags == 18 (я считаю, что это означает, что блоки все еще помечены как при воспроизведении)

Как я могу решить эту проблему?

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

1

Решение

Описанное поведение может произойти, если вы не позвоните

waveOutUnprepareHeader

на каждый буфер, который вы использовали перед использованием

waveOutClose

Поле флага _dwFlags, кажется, указывает, что буферы все еще помещены в очередь (WHDR_INQUEUE | WHDR_PREPARED) try:

waveOutReset

перед неподготовленными буферами.

После анализа вашего кода я обнаружил две проблемы / ошибки, которые не связаны с waveOut (забавно, вы используете C ++ 11, но самый старый медиа-интерфейс). Вы используете вектор в качестве буфера. Во время некоторых операций вызова вектор копируется! Я обнаружил одну ошибку:

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

вместо:

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

который заставляет копию вектора.
Старайтесь избегать использования векторов, если вы планируете использовать его в основном в качестве rawbuffer. Лучше использовать std :: unique_pointer в качестве указателя буфера.

Ваш обратный вызов в регистраторе не отслеживается мьютексом и не проверяет, был ли уже вызван деструктор. Разрушение происходит во время обратного вызова (в основном), что приводит к исключению.

Для вашей тестовой программы вернитесь назад и используйте необработанные указатели и статические обратные вызовы, прежде чем обвинять waveOut. Ваш код неплох, но первая ошибка уже показывает, что небольшая ошибка приведет к непредсказуемым ошибкам. Поскольку вы также организуете свои буферы в массиве std ::, я буду искать там ошибки. Я предполагаю, что вы делаете непреднамеренную копию всего массива буферов, не готовя неправильные буферы.

У меня не было времени копать глубже, но я думаю, что это проблемы.

2

Другие решения

В конце концов мне удалось найти свою проблему, это было вызвано множественными ошибками и тупиком. Я задокументирую, что здесь произошло, чтобы люди могли учиться на этом в будущем
Я понял, что происходит, когда исправляю ошибки в образце:

  • вызовите waveInStop () перед waveInClose () в ~ Recorder.cpp
  • дождитесь, пока все буферы будут иметь флаг WHDR_DONE, прежде чем вызывать waveOutClose () в ~ PcmPlayback.

После этого образец работал нормально и не отображать поведение флага WHDR_DONE, никогда не отмечаемого

В моей основной программе такое поведение было вызвано тупиком, который возникает в следующей ситуации:

  • У меня есть вектор объектов, представляющих каждого пира, с которым я передаю аудио
  • Каждый объект имеет класс воспроизведения
  • Этот вектор защищен мьютексом

Запись обратного вызова:

  • mutex.lock ()
  • отправить аудиопакет каждому пиру

Удалить Peer:

  • mutex.lock ()
  • ~ PcmPlayback
  • подождите, пока не будут помечены флаги WHDR_DONE

Когда я удаляю одноранговый узел, возникает взаимная блокировка, блокировка мьютекса и обратный вызов рекордера тоже пытается получить блокировку.

  • Обратите внимание, что это часто происходит, потому что буфер воспроизведения обычно (~ 4 * 20 мс), в то время как рекордер имеет частоту 20 мс.
  • В ~ PcmPlayback буферы никогда не будут помечены как WHDR_DONE, и любые вызовы API WaveOut никогда не вернутся, поскольку API WaveOut ожидает завершения обратного вызова Recorder, который, в свою очередь, ожидает mutex.lock (), вызывая взаимоблокировку ,
0

По вопросам рекламы [email protected]