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

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

Редактировать 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):

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

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

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

Другие вопросы по тегам