我正在用C#在Windows 10下编写一个程序来捕获来自MIDI设备的SysEx消息。我使用回调函数,如下所示:
private delegate void MidiInProc(
int handle,
uint msg,
int instance,
int param1,
int param2);
[DllImport("winmm.dll")]
private static extern int midiInOpen(
out int handle,
int deviceID,
MidiInProc proc,
int instance,
int flags);
private int hHandle;
public MidiInput(int deviceID)
{
MidiInProc midiInProc = MidiInProcess;
int hResult = midiInOpen(
out hHandle,
deviceID,
midiInProc,
0,
CALLBACK_FUNCTION | MIDI_IO_STATUS);
for (int i = 0; i < NUM_MIDIHDRS; ++i)
{
InitMidiHdr(i);
}
}
private void MidiInProcess(int hMidiIn, uint uMsg, int dwInstance, int dwParam1, int dwParam2)
{
switch (uMsg)
{
case MIM_OPEN:
break;
case MIM_CLOSE:
break;
case MIM_DATA:
QueueData(dwParam1);
break;
case MIM_MOREDATA:
QueueData(dwParam1);
break;
case MIM_LONGDATA:
QueueLongData((IntPtr)dwParam1);
break;
case MIM_ERROR:
throw new ApplicationException(string.Format("Invalid MIDI message: {0}", dwParam1));
case MIM_LONGERROR:
throw new ApplicationException("Invalid SysEx message.");
default:
throw new ApplicationException(string.Format("unexpected message: {0}", uMsg));
}
}InitMidiHdr方法在堆中创建NUM_MIDIHDRS头和缓冲区,并通过调用MidiInPrepareHeader和MidiInAddBuffer将它们的地址传递给驱动程序。当接收到SysEx数据时,回调切换到MIM_LONGDATA情况,并将缓冲区地址排队到另一个线程,该线程对其进行排队列,对其进行处理,然后将其传递回驱动程序,以便通过对MidiInAddBuffer的另一次调用进一步使用。
现在,一些程序不使用单独的线程,而是处理回调线程上的SysEx数据。据我所读,这大部分时间起作用,但并不总是起作用,并且反对MSDN的建议:“应用程序不应该从回调函数内部调用任何多媒体函数。”这可能不是当代驱动程序的问题,但在过去,一些用户在从回调中直接调用MidiInAddBuffer时报告过死锁。
但是..。
当我使用一个工作线程调用MidiInAddBuffer时,我想不出一种方法来保证它能够跟上对回调的调用。当然,如果工作人员所做的唯一事情是返回缓冲区,它很可能会在回调之前保持,但是依赖一个线程来保持在另一个线程的前面而没有显式同步是一种错误的做法。(让回调等待来自工作人员的信号,说明它返回了缓冲区,因为MidiInAddBuffer在从另一个线程调用时会阻塞,直到回调返回为止,这会导致死锁,如果您在辅助线程中对来自MidiInAddBuffer的返回设置返回条件。)因此,在该场景中,当MIDI设备仍在发送SysEx数据时,驱动程序可能会耗尽缓冲区。(实际上,即使在回调完成所有处理本身的情况下,它也可能落后,驱动程序在设备停止发送SysEx数据之前耗尽了所有的缓冲区。)
我已经发现了几个开源项目,它们使用一个工作人员来调用MidiInAddBuffer,但这两个项目似乎都依赖于该工作人员在调用回调之前保持领先。实际上,在一种情况下,我向工作人员添加了一个50 of的睡眠调用,其结果是它远远落后于回调线程,这最终意味着只有大约一半的SysEx消息被回调。显然,驱动程序不会在内部缓冲SysEx数据,并且在没有缓冲区可用时,会抛出SysEx数据,直到获得另一个缓冲区为止。
现在,内存很便宜,所有这些,一个“解决方案”是有大量的缓冲区。然而,我的Behringer BCF2000在一个转储中发送了将近500个不同的SysEx消息,每个消息都在自己的缓冲区中。这不是一个不可能提供的数字,但它需要猜测多少才是真正足够的(而且,考虑到人们使用SysEx消息做的一些奇特的事情,比如传递音频样本,这种方法可能会变得难以处理)。
唉,当我的测试显示我的代码跟不上的时候,MIM_LONGERROR案例就不会被调用,所以这是没有帮助的。
所以,我的问题是:如果我的代码无法跟上MIDI设备驱动程序消耗SysEx缓冲区的速度,而我最终丢失了一些SysEx数据,那么至少有什么方法可以检测到我错过了这些数据吗?
发布于 2017-08-30 10:22:34
缺少的SysEx缓冲区没有报告机制。
以MIDI 3125字节/s的速度,50 ms延迟对应于156字节的缓冲区;如果实际消息较短,则保证落后。但是真正的代码不会有如此一致的延迟,所以这不是一个现实的测试。您的线程可能会得到随机的调度延迟,但只要有足够的缓冲区排队,这就没有问题。(如果其他一些优先级较高的代码根本无法执行您的代码,那么您无论如何也无能为力。)
https://stackoverflow.com/questions/45947999
复制相似问题