c# – Windows MIDI流媒体和SysEx

我正在使用C#中古老的Windows多媒体API(来自WinMM.dll的midiXyz函数).

在非流模式(midiOutOpen)打开Midi Out设备/端口后,发送带(midiOutLongMsg)的SysEx工作正常.

在流模式(midiStreamOpen)中打开Midi Out设备/端口后,使用midiOutLongMsg发送SysEx不起作用.

相反,midiOutLongMsg失败,错误MMSYSERR_NOTSUPPORTED(= 8).
错误文本为:“不支持此功能.使用Capabilities功能确定驱动程序支持的功能和消息.”

但是,根据MSDN,(midiOutLongMsg)也应该使用流句柄.
Jeff Glatt’s excellent MIDI information pages 还声称,SysEx和流媒体可以一起使用(see end of page).

通过使用(midiStreamOut)midiStreamOut对它们进行排队来发送缓冲的SysEx消息正常工作.
但是,我需要/想要使用midiOutLongMsg直接发送SysEx.

我已经检查了各种开源Midi库(托管以及非托管),几个Midi驱动程序源,甚至WINE的WinMM.dll源代码,但找不到任何提示我做错了什么.

为了在尽可能小的代码中重现我的问题,我删除了所有回调,无准备,清理和发布的东西,并在一个函数中压缩了几个类.
以下代码打开第一个Midi设备/端口并尝试发送“GM Mode On”SysEx消息:

2014年1月12日更新:请参阅下面的代码版本3!

public static class MidiTest { // version 1 - x86/32 bit only

  public static void Test () {
    int moID = 0; // midi out device/port ID
    int moHdl; // midi out device/port handle
#if !true
    // SysEx via midiOutLongMsg works
    Chk (WinMM.midiOutOpen (out moHdl, moID, null, 0, 0)); // open midi out in non-stream mode
#else
    // SysEx via midiOutLongMsg fails
    Chk (WinMM.midiStreamOpen (out moHdl, ref moID, 1, null, 0, 0)); // open midi out in stream mode
#endif
    byte [] sx = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; // GM On sysex
    int shdr = Marshal.SizeOf (typeof (MidiHdr)); // hdr size
    var mhdr = new MidiHdr (); // allocate managed hdr
    mhdr.bufferLength = mhdr.bytesRecorded = sx.Length; // length of message bytes
    mhdr.data = Marshal.AllocHGlobal (mhdr.bufferLength); // allocate native message bytes
    Marshal.Copy (sx, 0, mhdr.data, mhdr.bufferLength); // copy message bytes from managed to native memory
    IntPtr nhdr = Marshal.AllocHGlobal (shdr); // allocate native hdr
    Marshal.StructureToPtr (mhdr, nhdr, false); // copy managed hdr to native hdr
    Chk (WinMM.midiOutPrepareHeader (moHdl, nhdr, shdr)); // prepare native hdr
    Chk (WinMM.midiOutLongMsg (moHdl, nhdr, shdr)); // send native message bytes
  } // Test

  static void Chk (int f) {
    if (0 == f) return;
    var sb = new StringBuilder (256); // MAXERRORLENGTH
    var s = 0 == WMM.midiOutGetErrorText (f, sb, sb.Capacity) ? sb.ToString () : String.Format ("MIDI Error {0}.", f);
    System.Diagnostics.Trace.WriteLine (s);
  }

  [StructLayout (LayoutKind.Sequential)]
  internal struct MidiHdr { // sending long MIDI messages requires a header
    public IntPtr data; // native pointer to message bytes, allocated on native heap
    public int bufferLength; // length of buffer 'data'
    public int bytesRecorded; // actual amount of data in buffer 'data'
    public int user; // custom user data
    public int flags; // information flags about buffer
    public IntPtr next; // reserved
    public int reserved; // reserved
    public int offset; // buffer offset on callback
    [MarshalAs (UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] reservedArray; // reserved
  } // struct MidiHdr

  internal sealed class WinMM { // native MIDI calls from WinMM.dll
    public delegate void CB (int hdl, int msg, int inst, int p1, int p2); // callback
    [DllImport ("winmm.dll")] public static extern int midiStreamOpen (out int hdl, ref int devID, int reserved, CB proc, int inst, int flags);
    [DllImport ("winmm.dll")] public static extern int midiOutOpen (out int hdl, int devID, CB proc, int inst, int flags);
    [DllImport ("winmm.dll")] public static extern int midiOutPrepareHeader (int hdl, IntPtr pHdr, int sHdr);
    [DllImport ("winmm.dll")] public static extern int midiOutLongMsg (int hdl, IntPtr pHdr, int sHdr);
    [DllImport ("winmm.dll")] public static extern int midiOutGetErrorText (int err, StringBuilder msg, int sMsg);
  } // class WinMM

} // class MidiTest

问题1:当Midi设备/端口以流模式打开时(midiStreamOpen),是否可以通过midiOutLongMsg发送SysEx?

问题2:如果是,任何我不知道的内容?

问题3:我没有在流模式下使用MIDI找到很多来源.所以,如果您在流模式下使用MIDI输出知道一些开源库,请给我一个链接,以便我可以比较..

谢谢

更新(2013年10月19日):

嗨CL,

感谢您的回答.是的,也许微软的某个人在维护WinMM.dll时搞砸了一些东西 – 但我认为我遗漏某些东西的概率更高,因为

a)有一个旧的手册“Windows NT DDK – 多媒体驱动程序”(可用here),它比当前的MSDN页面更详细地描述了WinMM的内容.第56页显示了WinMM.dll作为应用程序和MIDI /音频驱动程序之间的中间层的图表. WinMM的主要工作是将MIDI数据从应用程序传递到其中一个MIDI / Audio驱动程序.当MIDI驱动程序是外部键盘/合成器/音调发生器的端口驱动程序时,WinMM无法更改MIDI数据.第94页描述了驱动程序消息MODM_LONGDATA及其参数 – 它与midiOutLongMsg的参数非常相似.这限制了在WinMM.dll中弄乱一些东西的机会.

b)来自midiOutLongMsg的WinMM.dll中的代码路径被调用以调用带有MODM_LONGDATA的驱动程序在midiOutOpen之后正常工作,但在midiStreamOpen之后没有.结果代码是MMSYSERR_NOTSUPPORTED – 它告诉我在WinMM.dll的代码路径的开头有一些健全性检查,就像我一样

if (whatever_weird_condition) return MMSYSERR_NOTSUPPORTED;

而且,what_weird_condition最有可能是我应该做的事情,但还没有做过……

c)如果MIDI驱动程序本身不支持流输出(可选),则WinMM会执行从缓冲/排队输出(midiStreamOut)到更简单的非流驱动程序调用(不是可选的)的转换.我机器上的8个MIDI驱动程序都没有支持流本身,所有都依靠WinMM来完成.流式短消息和长消息都可以正常工作.

d)我的测试代码在Windows 7和Windows XP上的行为完全相同.如果微软搞砸了,那么这个bug肯定是在很久以前(XP之前)发生的.而且我是第一个找到它的人(经过多年),或者其他人保守秘密
ungoogleable.

e)我的测试代码与我机器上的所有8个midi驱动程序完全相同.这告诉我,这很可能不是驱动程序问题.

f)多年的调试告诉我,如果事情不能正常工作,问题最有可能出现在我的屏幕上.. ;-P

解决方法:

更新:我为没有早点回来而道歉.我在工作中被淹没了.是的,你说得对,现在失败了.也许那天晚上我来得太晚了,但我无法理解它是如何工作的,即使它是.我还需要以流模式发送sysex,但我的应用程序还没有完成,只是在非流模式下(midiOutOpen).我将继续关注它,看看我是否能找到解决方法.您是否必须使用sysex主音量,还是可以使用CC:7音量控制?当然,这对sysex没有帮助,但短消息可以在流模式下通过.哦,感谢您的更新,我还获得了编译和运行x86或x64(AnyCPU)的代码.

原始信息:我不知道您是否仍然感兴趣,但我认为以下代码可能会回答您的问题.我把你的旧代码放在注释// PREVIOUS CODE和注释// NEW CODE下的新代码下.

此外,对于x86,标头的大小不应包含数据.我知道x86的大小是0x40,但我仍然想找出最好的编码方式,所以如果你有任何想法让我知道.

我只是想出了另一个应用程序,所以我还没有充实它,但我运行了这个代码,它似乎适合你.我喜欢这个旧dll中的流媒体模式.这是非常精确的,如果你使用双缓冲,你可以实时…你也可以在流模式下发送短消息,就像你的midiout一样.

提示:下面的代码是版本2,部分与x86 / x64 32/64位兼容. (MillKa)

using System;

using System.Collections.Generic;

using System.Text;

using System.Runtime.InteropServices;

using System.Diagnostics;

public static class MidiTest
{

    public static void Test() 
    {
        int moID = 0; // midi out device/port ID

        //PREVIOUS CODE
        //int moHdl; // midi out device/port handle
        //NEW CODE
        IntPtr moHdl = IntPtr.Zero;

#if !true
    // SysEx via midiOutLongMsg works
    Chk (WinMM.midiOutOpen (out moHdl, moID, null, 0, 0)); // open midi out in non-stream mode
#else
        // SysEx via midiOutLongMsg fails
        //PREVIOUS CODE
        //Chk(WinMM.midiStreamOpen(out moHdl, ref moID, 1, null, 0, 0)); // open midi out in stream mode
        //NEW CODE
        IntPtr instance = IntPtr.Zero;
        Chk(WinMM.midiStreamOpen(out moHdl, ref moID, 1, null, instance, 0)); // open midi out in stream mode

#endif
        byte[] sx = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 }; // GM On sysex

        //PREVIOUS CODE
        //int shdr = Marshal.SizeOf(typeof(MidiHdr)); // hdr size
        //NEW CODE
        int shdr = 0x40; // hdr size

        var mhdr = new MidiHdr(); // allocate managed hdr
        mhdr.bufferLength = mhdr.bytesRecorded = sx.Length; // length of message bytes
        mhdr.data = Marshal.AllocHGlobal(mhdr.bufferLength); // allocate native message bytes
        Marshal.Copy(sx, 0, mhdr.data, mhdr.bufferLength); // copy message bytes from managed to native memory
        IntPtr nhdr = Marshal.AllocHGlobal(shdr); // allocate native hdr
        Marshal.StructureToPtr(mhdr, nhdr, false); // copy managed hdr to native hdr
        Chk(WinMM.midiOutPrepareHeader(moHdl, nhdr, shdr)); // prepare native hdr
        Chk(WinMM.midiOutLongMsg(moHdl, nhdr, shdr)); // send native message bytes
    } // Test

    static void Chk(int f)
    {
        if (0 == f) return;
        var sb = new StringBuilder(256); // MAXERRORLENGTH
        var s = 0 == WinMM.midiOutGetErrorText(f, sb, sb.Capacity) ? sb.ToString() : String.Format("MIDI Error {0}.", f);
        System.Diagnostics.Trace.WriteLine(s);
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct MidiHdr
    { // sending long MIDI messages requires a header
        public IntPtr data; // native pointer to message bytes, allocated on native heap
        public int bufferLength; // length of buffer 'data'
        public int bytesRecorded; // actual amount of data in buffer 'data'
        public int user; // custom user data
        public int flags; // information flags about buffer
        public IntPtr next; // reserved
        public int reserved; // reserved
        public int offset; // buffer offset on callback
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public int[] reservedArray; // reserved
    } // struct MidiHdr

    internal sealed class WinMM
    { // native MIDI calls from WinMM.dll
        public delegate void CB(int hdl, int msg, int inst, int p1, int p2); // callback

        //PREVIOUS CODE
        //[DllImport("winmm.dll")]
        //public static extern int midiStreamOpen(out int hdl, ref int devID, int reserved, CB proc, int inst, int flags);
        //[DllImport("winmm.dll")]
        //public static extern int midiOutOpen(out int hdl, int devID, CB proc, int inst, int flags);
        //[DllImport("winmm.dll")]
        //public static extern int midiOutPrepareHeader(int hdl, IntPtr pHdr, int sHdr);
        //[DllImport("winmm.dll")]
        //public static extern int midiOutLongMsg(int hdl, IntPtr pHdr, int sHdr);
        //[DllImport("winmm.dll")]
        //public static extern int midiOutGetErrorText(int err, StringBuilder msg, int sMsg);

        //NEW CODE
        #region winmm declarations
        [DllImport("winmm.dll")]
        public static extern int midiOutPrepareHeader(IntPtr handle,
            IntPtr headerPtr, int sizeOfMidiHeader);
        [DllImport("winmm.dll")]
        public static extern int midiOutUnprepareHeader(IntPtr handle,
            IntPtr headerPtr, int sizeOfMidiHeader);
        [DllImport("winmm.dll")]
        public static extern int midiOutOpen(out IntPtr handle, int deviceID,
            CB proc, IntPtr instance, int flags);
        [DllImport("winmm.dll")]
        public static extern int midiOutGetErrorText(int errCode,
            StringBuilder message, int sizeOfMessage);
        [DllImport("winmm.dll")]
        public static extern int midiOutClose(IntPtr handle);
        [DllImport("winmm.dll")]
        public static extern int midiStreamOpen(out IntPtr handle, ref int deviceID, int reserved,
            CB proc, IntPtr instance, uint flag);
        [DllImport("winmm.dll")]
        public static extern int midiStreamClose(IntPtr handle);
        [DllImport("winmm.dll")]
        public static extern int midiStreamOut(IntPtr handle, IntPtr headerPtr, int sizeOfMidiHeader);
        [DllImport("winmm.dll")]
        public static extern int midiOutLongMsg(IntPtr handle,
            IntPtr headerPtr, int sizeOfMidiHeader);
        #endregion

    } // class WinMM

} // class MidiTest
上一篇:如何发现插入DVD / CD驱动器的媒体类型? (Java)的


下一篇:14. Android MultiMedia框架完全解析 - NuPlayerDecoder与MediaCodec的交互