Проблема с тупиком waveOutWrite и waveOutGetPosition - PullRequest
2 голосов
/ 16 марта 2010

Я работаю над приложением, которое непрерывно воспроизводит аудио, используя 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 : я нашел немного об этой проблеме в Интернете, за исключением этих (без ответа) сообщений:

http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/c6a1e80e-4a18-47e7-af11-56a89f638ad7

Редактировать 3 : Ну, теперь я могу воспроизвести эту проблему по своему желанию. Если я позвоню waveOutGetPosition сразу после waveOutWrite (в следующей строке кода), приложение будет зависать каждый раз. Он также зависает особенно плохо - кажется, он на какое-то время блокирует всю мою ОС, а не только само приложение. Таким образом, кажется, что waveOutGetPosition блокируется, если это происходит в почти в то же время, что и waveOutWrite, а не только буквально в одно и то же время, что может объяснить, почему блокировки не работают для меня. Иш.

Ответы [ 3 ]

3 голосов
/ 16 марта 2010

Он блокируется внутри кода 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-файл вашего проекта.

1 голос
/ 09 мая 2013

Я использую NAudio и часто запрашиваю WaveOut.GetPosition(), а также вижу частые тупики при использовании стратегии Callback. По сути, это та же проблема, с которой сталкивался ОП, поэтому я полагаю, что это решение может помочь кому-то еще.

Я пытался использовать оконные стратегии (как отмечено в ответе), но звук зависал, когда в очередь сообщений передавалось много сообщений. Поэтому я перешел на стратегию Callback. Потом у меня начались тупики.

Я запрашиваю положение звука при 60 кадрах в секунду для синхронизации анимации, поэтому я довольно часто захожу в тупик (в среднем около 20 секунд). Примечание: Я уверен, что смогу уменьшить сумму, которую я называю API, но это не моя точка зрения!

Кажется, что вызовы winmm.dll все внутренне блокируются на одном объекте / дескрипторе. Если это предположение верно, то мне почти гарантирован тупик в NAudio. Вот сценарий с двумя потоками: A (поток пользовательского интерфейса); и B (нить обратного вызова в winmm.dll) и две блокировки waveOutLock (как в NAudio) и mmdll (блокировка, которую я предполагаю использует winmm.dll):

  1. A -> lock (waveOutLock) --- приобретено
  2. B -> блокировка (mmdll) для обратного вызова --- получена
  3. B -> обратный вызов в код пользователя
  4. B -> попытка блокировки (waveOutLock) - ожидание освобождения A
  5. A -> возобновлено из-за ожидания B
  6. A -> call waveOutGetPosition
  7. A -> попытка блокировки (mmdll) - взаимоблокировка

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

Для тех, кто заинтересован, я разветвил и изменил источник NAudio, чтобы включить мои изменения. Я использовал пул потоков, и звук иногда немного треснул. Это может быть связано с управлением потоками пула потоков, поэтому может быть решение, которое работает лучше.

0 голосов
/ 17 марта 2010

Решение этой проблемы было очень простым (благодаря Ларри Остерману): замените обратный вызов WndProc.

Метод waveOutOpen может принимать делегат (для обратного вызова) или дескриптор окна. Я использовал делегированный подход, который, по-видимому, по своей природе склонен к блокировке (имеет смысл, особенно в управляемом коде). Я мог просто сделать так, чтобы мой класс проигрывателя наследовал от Control и переопределить метод WndProc, и выполнять те же действия в этом методе, что и в обратном вызове. Теперь я могу звонить waveOutGetPosition навсегда, и он никогда не блокируется.

...