使用 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 设备?