У меня проблема с 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));
}
Описанное поведение может произойти, если вы не позвоните
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 ::, я буду искать там ошибки. Я предполагаю, что вы делаете непреднамеренную копию всего массива буферов, не готовя неправильные буферы.
У меня не было времени копать глубже, но я думаю, что это проблемы.
В конце концов мне удалось найти свою проблему, это было вызвано множественными ошибками и тупиком. Я задокументирую, что здесь произошло, чтобы люди могли учиться на этом в будущем
Я понял, что происходит, когда исправляю ошибки в образце:
После этого образец работал нормально и не отображать поведение флага WHDR_DONE, никогда не отмечаемого
В моей основной программе такое поведение было вызвано тупиком, который возникает в следующей ситуации:
Запись обратного вызова:
Удалить Peer:
Когда я удаляю одноранговый узел, возникает взаимная блокировка, блокировка мьютекса и обратный вызов рекордера тоже пытается получить блокировку.