使用 Windows MIDI API 出现问题(播放时没有回调)

Posted

技术标签:

【中文标题】使用 Windows MIDI API 出现问题(播放时没有回调)【英文标题】:Trouble using Windows MIDI API (no callbacks when playing) 【发布时间】:2011-04-20 07:56:24 【问题描述】:

我有一个 USB 连接的 MIDI 键盘。它在其他应用程序中运行良好。但是,在我自己的程序中却没有。 midiInOpen() 调用通过,我收到一个回调(来自打开设备),但在弹奏键盘时我没有收到任何回调。

通过使用 midiInGetDevCaps(),我可以看到我正在使用正确的设备。

有什么想法吗?其他(商业)应用程序是否可以使用其他 API?

static void CALLBACK myFunc(HMIDIIN handle, UINT uMsg,
                            DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) 
   printf("Callback!"\n);


int main() 

   unsigned long result;
   HMIDIIN       inHandle;

   result = midiInOpen(&inHandle, 0, (DWORD)myFunc, 0, CALLBACK_FUNCTION);
   if (result)
   
      printf("There was an error opening MIDI\n");
   

   while(1)  Sleep(1); 

【问题讨论】:

您确定它没有被调用是因为您设置了断点,还是因为 printf() 没有显示任何内容?当接收到 MIDI 数据时,您可以在此回调中执行有限的操作列表——API 文档建议使用 OutputDebugString();我不确定printf 在这种情况下是否应该工作。 【参考方案1】:

您需要致电midiInstart。您还需要发送消息。如果您调用 Sleep 并且不为输入提供服务,则不会发生任何事情。

这里有一点tutorial on Windows MIDI。

这是我为 Win MIDI 输入编写的一个类 (WinMidiIn) 的摘录(删除了许多错误处理,可以从它们作为参数传递的调用中推断出类成员类型):


    MMRESULT res = ::midiInOpen(&mMidiIn, mDeviceIdx, (DWORD_PTR)MidiInCallbackProc, (DWORD_PTR)this, 
        CALLBACK_FUNCTION | MIDI_IO_STATUS);
    if (MMSYSERR_NOERROR != res)
        return;

    const int kDataBufLen = 512;
    int idx;
    for (idx = 0; idx < MIDIHDR_CNT; ++idx)
    
        mMidiHdrs[idx].lpData = (LPSTR) ::malloc(kDataBufLen);
        mMidiHdrs[idx].dwBufferLength = kDataBufLen;

        res = ::midiInPrepareHeader(mMidiIn, &mMidiHdrs[idx], (UINT)sizeof(MIDIHDR));
        res = ::midiInAddBuffer(mMidiIn, &mMidiHdrs[idx], sizeof(MIDIHDR));
    

    res = ::midiInStart(mMidiIn);

    for (;;)
    
        DWORD result;
        MSG msg;

        // Read all of the messages in this next loop, 
        // removing each message as we read it.
        while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        
            // If it is a quit message, exit.
            if (msg.message == WM_QUIT)
                break;

            // Otherwise, dispatch the message.
            ::DispatchMessage(&msg);
        

        // Wait for any message sent or posted to this queue 
        // or for one of the passed handles be set to signaled.
        result = ::MsgWaitForMultipleObjects(1, &mDoneEvent, FALSE, INFINITE, QS_ALLINPUT);

        // The result tells us the type of event we have.
        if (result == (WAIT_OBJECT_0 + 1))
        
            // New messages have arrived. 
            // Continue to the top of the always while loop to 
            // dispatch them and resume waiting.
            continue;
        
        else if (WAIT_TIMEOUT == result)
            continue;
        else if (WAIT_OBJECT_0 == result)
            break; // done event fired
        else
            break; // ??
    

    res = ::midiInReset(mMidiIn);

    for (idx = 0; idx < MIDIHDR_CNT; ++idx)
    
        res = ::midiInUnprepareHeader(mMidiIn, &mMidiHdrs[idx], (UINT)sizeof(MIDIHDR));
        ::free(mMidiHdrs[idx].lpData);
        mMidiHdrs[idx].lpData = NULL;
    

    res = ::midiInClose(mMidiIn);
    mMidiIn = NULL;




void CALLBACK 
MidiInCallbackProc(HMIDIIN hmi, 
                          UINT wMsg, 
                          DWORD dwInstance, 
                          DWORD dwParam1, 
                          DWORD dwParam2)

    MMRESULT res;
    LPMIDIHDR hdr;
    WinMidiIn * _this = (WinMidiIn *) dwInstance;

    switch (wMsg)
    
    case MIM_DATA:
        //  dwParam1 is the midi event with status in the low byte of the low word
        //  dwParam2 is the event time in ms since the start of midi in
        // data: LOBYTE(dwParam1), HIBYTE(dwParam1), LOBYTE(HIWORD(dwParam1))
        break;
    case MIM_ERROR:
        break;
    case MIM_LONGDATA:
        //  dwParam1 is the lpMidiHdr
        //  dwParam2 is the event time in ms since the start of midi in
        hdr = (LPMIDIHDR) dwParam1;
        // sysex: (byte*)hdr->lpData, (int)hdr->dwBytesRecorded
        res = ::midiInAddBuffer(_this->mMidiIn, hdr, sizeof(MIDIHDR));
        break;
    case MIM_LONGERROR:
        hdr = (LPMIDIHDR) dwParam1;
        res = ::midiInAddBuffer(_this->mMidiIn, hdr, sizeof(MIDIHDR));
        break;
    

【讨论】:

所以我迟到了六年,但我自己还是第一次应对其中的一些问题。我看到您在回拨中致电midiInAddBuffer。这是有道理的,因为驱动程序不能再次使用您的缓冲区,直到您执行 DRWE。但是MidiInProc 上的 MSDN 文档说:“应用程序不应从回调函数内部调用任何多媒体函数,因为这样做会导致死锁。其他系统函数可以安全地从回调函数中调用。”打电话midiInAddBuffer 不违反这个限制吗? (另一方面,你还会在哪里称呼它?) @StevensMiller 看起来有人和你有同样的问题:social.msdn.microsoft.com/Forums/vstudio/en-US/…。并不是说这是一个答案,但我从来没有遇到过在回调中调用 midiInAddBuffer 以响应 sysex 数据的问题(自 1996 年左右以来) 嘿,感谢您回复我!我自己一直在回调中调用它,没有任何问题。但似乎有些人认为这是不安全的,至少对于某些驱动程序而言(可以想象调用导致对回调的无休止的递归系列调用,我想)。 github.com/jonathanslenders/fluidsynth/blob/master/src/drivers/… 的 cmets 提到了这一点。为了完全兼容,该代码(和其他一些代码)将缓冲区发布到工作线程,然后调用 midiInAddBuffer。 (哎呀,但它就在那里。)【参考方案2】:

你可以在这里找到一个例子https://gist.github.com/yoggy/1485181 我在这里发布代码以防链接失效

您可能缺少 midiInStart()

#include <SDKDDKVer.h>

#include <Windows.h>

#include <stdio.h>
#include <conio.h>

#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

void PrintMidiDevices()

    UINT nMidiDeviceNum;
    MIDIINCAPS caps;

    nMidiDeviceNum = midiInGetNumDevs();
    if (nMidiDeviceNum == 0) 
        fprintf(stderr, "midiInGetNumDevs() return 0...");
        return;
    

    printf("== PrintMidiDevices() == \n");
    for (unsigned int i = 0; i < nMidiDeviceNum; ++i) 
        midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
        printf("\t%d : name = %s\n", i, caps.szPname);
    
    printf("=====\n");


void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)

    switch(wMsg) 
    case MIM_OPEN:
        printf("wMsg=MIM_OPEN\n");
        break;
    case MIM_CLOSE:
        printf("wMsg=MIM_CLOSE\n");
        break;
    case MIM_DATA:
        printf("wMsg=MIM_DATA, dwInstance=%08x, dwParam1=%08x, dwParam2=%08x\n", dwInstance, dwParam1, dwParam2);
        break;
    case MIM_LONGDATA:
        printf("wMsg=MIM_LONGDATA\n"); 
        break;
    case MIM_ERROR:
        printf("wMsg=MIM_ERROR\n");
        break;
    case MIM_LONGERROR:
        printf("wMsg=MIM_LONGERROR\n");
        break;
    case MIM_MOREDATA:
        printf("wMsg=MIM_MOREDATA\n");
        break;
    default:
        printf("wMsg = unknown\n");
        break;
    
    return;


int main(int argc, char* argv[])

    HMIDIIN hMidiDevice = NULL;;
    DWORD nMidiPort = 0;
    UINT nMidiDeviceNum;
    MMRESULT rv;

    PrintMidiDevices();

    nMidiDeviceNum = midiInGetNumDevs();
    if (nMidiDeviceNum == 0) 
        fprintf(stderr, "midiInGetNumDevs() return 0...");
        return -1;
    

    rv = midiInOpen(&hMidiDevice, nMidiPort, (DWORD)(void*)MidiInProc, 0, CALLBACK_FUNCTION);
    if (rv != MMSYSERR_NOERROR) 
        fprintf(stderr, "midiInOpen() failed...rv=%d", rv);
        return -1;
    

    midiInStart(hMidiDevice);

    while(true) 
        if (!_kbhit()) 
            Sleep(100);
            continue;
        
        int c = _getch();
        if (c == VK_ESCAPE) break;
        if (c == 'q') break;
    

    midiInStop(hMidiDevice);
    midiInClose(hMidiDevice);
    hMidiDevice = NULL;

    return 0;

【讨论】:

以上是关于使用 Windows MIDI API 出现问题(播放时没有回调)的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 控制台应用程序枚举 UWP MIDI API 设备?

Windows 10 的音频和 MIDI API将统一

在 Windows 中使用 MIDI 流时出现问题

Java sound api - 扫描 MIDI 设备

从其他驱动程序获取输入流?图形输入板驱动程序,输出 Midi 以与 Web Midi API 一起使用

如何解析 Web MIDI API 输入消息 (onmidimesage)