waveOutWrite和waveOutGetPosition死锁问题

时间:2010-03-16 05:43:51

标签: c# audio waveoutwrite

我正在使用来自waveOut...的{​​{1}} API继续播放音频的应用。该应用程序使用“leapfrog”缓冲区,这些缓冲区基本上是一堆转储到音频队列中的样本数组。 Windows按顺序无缝播放它们,并且当每个缓冲区完成时,Windows会调用回调函数。在这个函数中,我将下一组样本加载到缓冲区中,然后处理它们,然后将缓冲区转储回音频队列。通过这种方式,音频可以无限播放。

出于动画目的,我正在尝试将winmm.dll合并到应用程序中(因为“缓冲完成”回调非常不规则,导致动画不稳定)。 waveOutGetPosition返回播放的当前位置,因此它非常精确。

问题在于,在我的应用程序中,调用waveOutGetPosition最终导致应用程序锁定 - 声音停止并且调用永远不会返回。我把事情简化为一个简单的应用程序来证明这个问题。您可以在此处运行该应用程序:

http://www.musigenesis.com/SO/waveOut%20demo.exe

如果你只是一遍又一遍地听到一点钢琴,它就能正常工作。这只是为了证明这个问题。这个项目的源代码在这里(所有的东西都在LeapFrogPlayer.cs中):

http://www.musigenesis.com/SO/WaveOutDemo.zip

第一个按钮以越级模式运行应用程序,而不调用waveOutGetPosition。如果单击此按钮,应用程序将永久播放而不会中断(X按钮将关闭它并将其关闭)。第二个按钮启动leapfrogger并启动一个调用waveOutGetPosition的窗体计时器并显示当前位置。单击此按钮,应用程序将运行一段时间,然后锁定。在我的笔记本电脑上,它通常会在15-30秒内锁定;最多花了一分钟。

我不知道如何解决这个问题,所以任何帮助或建议都会受到欢迎。我在这个问题上发现了很少的帖子,但似乎存在潜在的死锁,无论是对waveOutGetPosition的多次调用,还是同时发生的调用和waveOutGetPosition。我可能会经常调用它来让系统处理。

编辑:忘了提及,我在Windows Vista上运行它。在其他操作系统上可能根本不会发生这种情况。

编辑2 :除了这些(未答复的)帖子外,我在网上找不到这个问题:

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

编辑3 :嗯,我现在可以随意重现此问题。如果我在waveOutWrite之后立即调用waveOutGetPosition(在以下代码行中),应用程序每次都会挂起。它也以一种特别糟糕的方式挂起 - 它似乎锁定了我的整个操作系统一段时间,而不仅仅是应用程序本身。所以看起来waveOutWrite如果它出现在几乎waveOutGetPosition同时发生死锁,而不仅仅是字面上的同时,这可能解释了为什么锁无效为了我。 Yeesh。

3 个答案:

答案 0 :(得分:3)

它在mmsys API代码中死锁。当主线程忙于执行waveOutWrite()时,在回调死锁内调用waveOutGetPosition()。它是可修复的,你需要一个锁,所以这两个函数不能同时执行。将此字段添加到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 project.

下次创建项目的可上载.zip时,请使用Build + Clean。

答案 1 :(得分:1)

我正在使用NAudio并经常查询WaveOut.GetPosition(),并且在使用Callback策略时也会看到频繁的死锁。这与OP的问题基本相同,所以我认为这个解决方案可能对其他人有帮助。

我尝试使用基于窗口的策略(如答案中所述)但是当通过消息队列推送大量消息时,音频会断断续续。所以我切换到Callback策略。然后我开始陷入僵局。

我正在以60 fps的速度查询音频位置以同步动画,因此我经常会遇到死锁(平均约20秒)。 注意: 我确信我可以减少调用API的数量,但这不是我的观点!

似乎winmm.dll调用都在内部锁定在同一个对象/句柄上。如果这个假设成立,那么我几乎可以保证在NAudio中陷入僵局。这是两个线程的场景:A(UI线程);和Bwinmm.dll中的回调线程)和两个锁waveOutLock(如在NAudio中)和mmdll(我假设winmm.dll使用的锁):< / p>

  1. A - &gt;锁定(waveOutLock)---获得
  2. B - &gt;锁定(mmdll)用于回调---获得
  3. B - &gt;回调用户代码
  4. B - &gt;试图锁定(waveOutLock) - 等待A释放
  5. A - &gt;由于B等待而恢复
  6. A - &gt;调用waveOutGetPosition
  7. A - &gt;试图锁定(mmdll) - 死锁
  8. 我的解决方案是将回调中完成的工作委托给我自己的线程,以便回调可以立即返回并释放(假设的)mmdll锁。这似乎对我来说非常合适,因为僵局已经消失。

    对于那些感兴趣的人,我{NA}来源forked and modified包含我的更改。我使用了线程池,音频偶尔会有点疯狂。这可能是由于线程池线程管理,因此可能有更好的解决方案。

答案 2 :(得分:0)

对此的解决方案非常简单(感谢Larry Osterman):用WndProc替换回调。

waveOutOpen方法可以使用委托(用于回调)或窗口句柄。我正在使用委托方法,这显然本身就容易出现死锁(有意义,特别是在托管代码中)。我能够简单地让我的播放器类继承自Control并覆盖WndProc方法,并在我在回调中执行的方法中执行相同的操作。现在我可以永远打电话给waveOutGetPosition,它永远不会锁定。