Буферы 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(), вызывая взаимоблокировку,