Буферы 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));
}

2 ответа

Решение

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

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::, я буду искать там ошибки. Я предполагаю, что вы делаете непреднамеренную копию всего вашего массива буферов, не подготавливая неправильные буферы.

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

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

  • вызовите 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(), вызывая взаимоблокировку,
Другие вопросы по тегам