Проблема с тупиком waveOutWrite и waveOutGetPosition
Я работаю над приложением, которое непрерывно воспроизводит звук, используя waveOut...
API от winmm.dll
, Приложение использует буферы leapfrog, которые в основном представляют собой набор массивов сэмплов, которые вы помещаете в аудио-очередь. Windows воспроизводит их последовательно и по мере завершения каждого буфера Windows вызывает функцию обратного вызова. Внутри этой функции я загружаю следующий набор семплов в буфер, однако обрабатываю их, а затем сбрасываю буфер обратно в аудио-очередь. Таким образом, аудио воспроизводится бесконечно.
В целях анимации я пытаюсь включить waveOutGetPosition
в приложение (поскольку обратные вызовы "выполнено с помощью буфера" нерегулярны, чтобы вызвать прерывистую анимацию). waveOutGetPosition
возвращает текущую позицию воспроизведения, поэтому она является сверхточной.
Проблема в том, что в моем приложении звонки waveOutGetPosition
в конечном итоге приложение блокируется - звук останавливается, и звонок не возвращается. Я свел вещи к простому приложению, которое демонстрирует проблему. Вы можете запустить приложение здесь:
http://www.musigenesis.com/SO/waveOut%20demo.exe
Если вы слышите чуть-чуть пианино снова и снова, это работает. Это просто для того, чтобы продемонстрировать проблему. Исходный код этого проекта находится здесь (все мясо находится в LeapFrogPlayer.cs):
http://www.musigenesis.com/SO/WaveOutDemo.zip
Первая кнопка запускает приложение в режиме чехарды, не делая звонки waveOutGetPosition
, Если вы нажмете на это, приложение будет играть вечно, не прерываясь (кнопка X закроет его и выключит). Вторая кнопка запускает чехарду, а также запускает таймер форм, который вызывает waveOutGetPosition
и отображает текущую позицию. Нажмите на это, и приложение будет работать в течение короткого времени, а затем заблокировать. На моем ноутбуке он обычно зависает через 15-30 секунд; самое большее это заняло минуту.
Я понятия не имею, как это исправить, поэтому любая помощь или предложения будут приветствоваться. Я нашел очень мало сообщений по этому вопросу, но, похоже, существует потенциальная тупиковая ситуация, либо от нескольких звонков на waveOutGetPosition
или от звонков до этого и waveOutWrite
которые происходят одновременно. Возможно, я слишком часто это вызываю, чтобы система справилась.
Изменить: забыл упомянуть, я работаю на Windows Vista. Это может не произойти вообще на других ОС.
Изменить 2: я нашел немного об этой проблеме в Интернете, за исключением этих (без ответа) сообщений:
Редактировать 3: Ну, теперь я могу воспроизвести эту проблему по желанию. Если я позвоню waveOutGetPosition
незамедлительно после waveOutWrite
(в следующей строке кода) приложение зависает каждый раз. Он также зависает особенно плохо - кажется, он на какое-то время блокирует всю мою ОС, а не только само приложение. Итак, похоже, что waveOutGetPosition
взаимоблокировки, если это происходит почти одновременно с waveOutWrite
не только буквально одновременно, что может объяснить, почему у меня не работают замки. Иш.
3 ответа
Решение этой проблемы было очень простым (спасибо Ларри Остерману): замените обратный вызов на WndProc.
Метод waveOutOpen может принимать делегат (для обратного вызова) или дескриптор окна. Я использовал делегированный подход, который, по-видимому, по своей природе склонен к взаимоблокировке (имеет смысл, особенно в управляемом коде). Я смог просто получить свой класс игрока наследовать от Control
и переопределить WndProc
метод, и делать то же самое в этом методе, что я делал в обратном вызове. Теперь я могу позвонить waveOutGetPosition
навсегда, и это никогда не запирает.
Он блокируется внутри API-кода mmsys. Вызов waveOutGetPosition() внутри обратных вызовов блокируется, когда основной поток занят выполнением waveOutWrite(). Это исправимо, вам понадобится блокировка, чтобы эти две функции не могли выполняться одновременно. Добавьте это поле в LeapFrogPlayer:
private object mLocker = new object();
И используйте его в GetElapsedMilliseconds():
if (!noAPIcall)
{
lock (mLocker) {
ret = WaveOutX.waveOutGetPosition(_hWaveOut, ref _timestruct,
_timestructsize);
}
}
и HandleWaveCallback():
// play the next buffer
lock (mLocker) {
int ret = WaveOutX.waveOutWrite(_hWaveOut, ref _header[_currentBuffer],
Marshal.SizeOf(_header[_currentBuffer]));
if (ret != WaveOutX.MMSYSERR_NOERROR) {
throw new Exception("error writing audio");
}
}
Это может иметь побочные эффекты, хотя я их не заметил. Взгляните на проект NAudio.
Пожалуйста, используйте Build + Clean при следующем создании.zip вашего проекта.
Я использую NAudio
и запрос WaveOut.GetPosition()
часто, а также видя частые тупики при использовании Callback
стратегия. По сути, это та же проблема, что и у ОП, поэтому я думаю, что это решение может помочь кому-то еще.
Я попытался использовать оконные стратегии (как отмечено в ответе), но звук зависал, когда в очередь сообщений передавалось много сообщений. Поэтому я перешел на Callback
стратегия. Тогда я начал получать тупики.
Я запрашиваю позицию звука при 60 кадрах в секунду для синхронизации анимации, поэтому я захожу в тупик довольно регулярно (в среднем около 20 секунд на бег). Примечание: я уверен, что смогу уменьшить сумму, которую я называю API, но это не моя точка зрения!
Кажется, что winmm.dll
все вызовы внутренне блокируются одним и тем же объектом / дескриптором. Если это предположение верно, то я почти гарантирован тупик в NAudio. Вот сценарий с двумя потоками: A
(Пользовательский интерфейс); а также B
(ответный поток в winmm.dll
) и два замка waveOutLock
(как в НАудио) и mmdll
(блокировка, которую я предполагаю, использует winmm.dll):
- A -> блокировка (waveOutLock) --- получено
- B -> блокировка (mmdll) для обратного вызова --- приобретена
- B -> обратный вызов в код пользователя
- B -> попытка блокировки (waveOutLock) - ожидание освобождения A
- A -> возобновлено из-за ожидания B
- A -> вызов waveOutGetPosition
- A -> попытка блокировки (mmdll) - взаимоблокировка
Мое решение состояло в том, чтобы делегировать работу, выполненную в обратном вызове, моему собственному потоку, чтобы обратный вызов мог немедленно вернуться и освободить (гипотетический) mmdll
замок. Это, кажется, работает отлично для меня, поскольку тупик ушел.
Для тех, кто заинтересован, я добавил и изменил источник NAudio, чтобы включить мои изменения. Я использовал пул потоков, и звук иногда немного треснул. Это может быть связано с управлением потоками пула потоков, поэтому может быть решение, которое работает лучше.