C# waveOutWrite和waveOutGetPosition死锁问题

C# waveOutWrite和waveOutGetPosition死锁问题,c#,audio,waveoutwrite,C#,Audio,Waveoutwrite,我正在使用winmm.dll中的waveOut…API开发一个连续播放音频的应用程序。该应用程序使用“蛙跳”缓冲区,基本上是一组样本数组,您可以将其转储到音频队列中。Windows按顺序无缝地播放它们,当每个缓冲区完成时,Windows调用回调函数。在这个函数中,我将下一组样本加载到缓冲区中,然后处理它们,然后将缓冲区转储回音频队列。这样,音频将无限期播放 出于动画的目的,我试图将waveOutGetPosition合并到应用程序中(因为“buffer done”回调非常不规则,足以导致动画抖动

我正在使用
winmm.dll
中的
waveOut…
API开发一个连续播放音频的应用程序。该应用程序使用“蛙跳”缓冲区,基本上是一组样本数组,您可以将其转储到音频队列中。Windows按顺序无缝地播放它们,当每个缓冲区完成时,Windows调用回调函数。在这个函数中,我将下一组样本加载到缓冲区中,然后处理它们,然后将缓冲区转储回音频队列。这样,音频将无限期播放

出于动画的目的,我试图将
waveOutGetPosition
合并到应用程序中(因为“buffer done”回调非常不规则,足以导致动画抖动)
waveOutGetPosition
返回当前播放位置,因此非常精确

问题是,在我的应用程序中,调用
waveOutGetPosition
最终会导致应用程序锁定—声音停止,调用永远不会返回。我已经把事情归结为一个简单的应用程序来演示这个问题。您可以在此处运行应用程序:

如果你只是一遍又一遍地听到一小段钢琴声,它就起作用了。这只是为了说明问题。该项目的源代码在这里(所有内容都在LeapFrogPlayer.cs中):

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

我不知道如何解决这个问题,所以欢迎提供任何帮助或建议。我发现关于这个问题的帖子很少,但似乎有一个潜在的死锁,要么是对
waveOutGetPosition
的多次调用,要么是同时发生的对该调用和
waveOutWrite
的调用。可能是我调用这个太频繁了,系统无法处理

编辑:忘了提一下,我正在Windows Vista上运行这个。这在其他操作系统上可能根本不会发生

编辑2:我在网上几乎找不到关于这个问题的信息,除了这些(未回答的)帖子:


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

它在mmsys API代码中死锁。当主线程忙于执行waveOutWrite()时,在回调内调用waveOutGetPosition()会导致死锁。它是可修复的,您需要一个锁,以便这两个函数不能同时执行。将此字段添加到LeapFrogPlayer:

    private object mLocker = new object();
并在GetElapsedMilliseconds()中使用它:

和HandleWaveCallback():

这可能有副作用,但我没有注意到。看一看


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

解决方案非常简单(感谢Larry Osterman):将回调替换为WndProc


waveOutOpen方法可以接受委托(用于回调)或窗口句柄。我使用的是委托方法,它显然天生就容易死锁(特别是在托管代码中)。我可以简单地让我的player类从
Control
继承并重写
WndProc
方法,并在这个方法中执行与回调中相同的操作。现在,我可以永远调用waveOutGetPosition,它永远不会锁定。

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

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

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

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

  • A->lock(waveOutLock)--获得
  • B->回调锁定(mmdll)--已获取
  • B->回调到用户代码中
  • B->尝试锁定(waveOutLock)--等待A释放
  • A->由于B等待而恢复
  • A->调用waveOutGetPosition
  • A->尝试锁定(mmdll)--死锁
  • 我的解决方案是将回调中完成的工作委托给我自己的线程,以便回调可以立即返回并重新发布
            if (!noAPIcall)
            {
              lock (mLocker) {
                ret = WaveOutX.waveOutGetPosition(_hWaveOut, ref _timestruct,
                    _timestructsize);
              }
            }
    
            // 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");
              }
            }