如何在MidiInProc中检测错过的SysEx数据?

时间:2017-08-29 20:41:59

标签: c# windows multithreading callback midi

我正在Windows 10下用C#编写程序来从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标头和缓冲区,并通过调用MidiInPrepareHeaderMidiInAddBuffer将其地址传递给驱动程序。当收到SysEx数据时,回调切换到MIM_LONGDATA情况并将缓冲区地址排队到另一个线程,该线程将其出列,处理它,然后通过另一个调用{{1}将其传递回驱动程序以供进一步使用}}

现在,一些程序不使用单独的线程,而是在回调线程上处理SysEx数据。根据我的阅读,这大部分时间都有效,但并非总是如此,并且与MSDN's advice相反:&#34;应用程序不应该从回调函数[。]&#34;内部调用任何多媒体函数。对于当代驱动程序而言,这可能不是问题,但是一些用户在过去直接从回调中调用MidiInAddBuffer时报告了死锁。

但是...

当我使用工作线程来呼叫MidiInAddBuffer时,我无法想出一种方法来保证它能够跟上对回调的调用。当然,如果工作人员所做的事情是返回缓冲区,它可能会保持在回调之前,但依赖于一个线程在没有显式同步的情况下保持领先于另一个线程是一种不好的做法。 (让回调等待来自工作者的信号它已经返回缓冲区不起作用,因为MidiInAddBuffer阻塞从另一个线程调用直到回调返回,如果你调整它的话会导致死锁返回工作线程中MidiInAddBuffer的返回。)因此,在MIDI设备仍在发送SysEx数据时,驱动程序在缓冲区用完时可能会出现这种情况。 (实际上,即使在回调完成所有处理的情况下,它也可能落后,驱动程序在设备停止发送SysEx数据之前使用其所有缓冲区。)

我发现了一些使用worker调用MidiInAddBuffer的开源项目,但两者似乎都依赖于worker来保持对回调的调用。事实上,在一个案例中,我向工作者添加了50ms的睡眠调用,结果是它远远落后于回调线程,这最终意味着只有大约一半的SysEx消息进入了回调。显然,驱动程序不会在内部缓冲SysEx数据,当它没有剩余的缓冲区时,会抛出SysEx数据,直到它获得另一个缓冲区。

现在,记忆力便宜,所有这一切,一个解决方案&#34;是有一个很多的缓冲区。但是,我的Behringer BCF2000在单个转储中发送几乎 500 个不同的SysEx消息,每个消息都在自己的缓冲区中。这不是一个不可能提供的数字,但它需要猜测有多少是真的足够(并且,鉴于人们使用SysEx消息做的一些奇特的事情,如传递音频样本,这样的方法可能得到笨重)。

唉,当我的测试显示我的代码没有跟上时,MidiInAddBuffer案例永远不会被调用,所以没有帮助。

所以,这就是我的问题:如果我的代码无法跟上MIDI设备驱动程序消耗SysEx缓冲区的速度,我最终错过了一些SysEx数据,那么任何方式至少检测到我错过了那些数据?

1 个答案:

答案 0 :(得分:2)

没有错过SysEx缓冲区的报告机制。

在3155字节/秒的MIDI速度下,50毫秒延迟对应于156字节的缓冲区;如果实际消息较短,则可以保证落后。 但实际代码不会有这样一致的延迟,所以这不是一个现实的测试。您的线程可能会出现随机调度延迟,但只要您有足够的缓冲区排队,这就没问题了。 (如果其他具有更高优先级的代码阻止您的代码被执行,那么无论如何都无法做到。)