为什么MIDI音序器在Windows 10上无法播放第二次?



多年来,我一直使用Windows的高级MIDI界面在游戏中播放MIDI文件作为背景音乐。现在我从几个升级到Windows 10的人那里听说,背景音乐第一次会播放(在启动程序后),但一旦它完成,它就无法再次启动(或开始播放下一个MIDI文件)。我自己还没有安装Windows 10,但我已经组装了几个调试程序,当程序第二次尝试启动MIDI文件播放时,在PlayMusic()中调用:

mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);

返回值343 (MCIERR_SEQ_NOMIDIPRESENT)。但是,如果程序退出并再次启动,则可以使后台MIDI再次播放。

我有两个函数:PlayMusic(char *fname)和StopMusic(),它们启动和停止MIDI文件播放,以及一个全局变量MusicPlaying,它跟踪音乐文件当前是否正在播放。然后,主窗口处理程序处理MM_MCINOTIFY消息,当接收到mci_notify_success时,它向我的主代码排队通知音乐完成,然后主代码最终将对PlayMusic()进行另一个调用(如果它想的话)以再次启动文件播放。下面是代码块(MusicPlaying是全局变量,它知道MIDI文件是否处于活动状态):

//*********************************************************
void StopMusic() {
    if( MusicPlaying ) {
        mciSendCommand(MCIwDeviceID, MCI_STOP, MCI_WAIT, 0);
        mciSendCommand(MCIwDeviceID, MCI_CLOSE, MCI_WAIT, 0);
        MusicPlaying = 0;
    }
}
//**********************************************************************
void PlayMusic(char *pMem) {
    MCIERROR        dwReturn;
    StopMusic();    // stop any previously playing music
    // BuildPath() just adds the appropriate folder info for the file
    BuildPath(pMem, DIR_MUSIC, FALSE);
// Open the device by specifying the device name and device element.
// MCI will attempt to choose the MIDI Mapper as the output port.
    mciOpenParms.dwCallback = 0;
    mciOpenParms.wDeviceID = 0;
    mciOpenParms.lpstrDeviceType = "sequencer";//NULL;
    mciOpenParms.lpstrElementName = (TCHAR *)TmpPath;
    mciOpenParms.lpstrAlias = NULL;
    dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_WAIT | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &mciOpenParms);
    if( dwReturn ) return; // Failed to open device, bail out
// Begin playback. The window procedure function for the parent window
// will be notified with an MM_MCINOTIFY message when playback is
// complete. At that time, the window procedure closes the device.
    MCIwDeviceID = mciOpenParms.wDeviceID;
    mciPlayParms.dwCallback = (DWORD)(hWndMain);
    dwReturn = mciSendCommand(MCIwDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms);
    if( dwReturn ) {        // if error
        mciSendCommand(MCIwDeviceID, MCI_CLOSE, 0, 0);  // close MCI device
        return; // and bail out
    }
    MusicPlaying = 1;
}

和在主窗口消息处理器中,当MIDI文件播放完成时:

case MM_MCINOTIFY:
    //***** SEE "NOTE" BELOW FOR DEBUG CODE INSERTED HERE *****
    // various MIDI messages, we only care about termination
    switch( wParam ) {
     case MCI_NOTIFY_ABORTED:               // value of 4
     case MCI_NOTIFY_FAILURE:
     case MCI_NOTIFY_SUPERSEDED:
         break;
     case MCI_NOTIFY_SUCCESSFUL:            // value of 1
         mciSendCommand(MCIwDeviceID, MCI_CLOSE, 0, 0);
         MusicPlaying = 0;
         AddMsg(KHDR_MUSIC_DONE, 0, 0, 0);       // Queue msg that music finished
         break;
     default:
         break;
    }
    return 0;

事件的正常顺序是:

Main game code calls PlayMusic() to start a MIDI playing
    (Nothing in StopMusic() since nothing's playing first time)
    MCI_OPEN
    MCI_PLAY
    MusicPlaying = 1;
MainWnd MM_MCINOTIFY MCI_NOTIFY_SUCCESSFUL when MIDI finished
    MCI_CLOSE
    MusicPlaying = 0;
    queue KHDR_MUSIC_DONE msg to main game code
Main game code eventually sends another PlayMusic() command

如果某些内容导致主游戏代码过早地停止音乐,那么它将调用StopMusic(),这将:

MCI_STOP
MCI_CLOSE
MusicPlaying = 0;

注意:(参见上面MM_MCINOTIFY中的"**** See 'NOTE'…"行)作为调试的一部分,我在此时插入了一个MessageBox调用来显示正在接收的通知。win10的用户看到的消息与我在早期版本的Windows上看到的完全相同:当成功发生时,wParam=1和lParam=1,当ABORTED发生时,wParam=4和lParam=1。但这里有一个问题:此时出现了MessageBox,然后当用户单击MessageBox上的OK时,MIDI文件就会重新启动!我的第一个想法是,这是MessageBox出现和被点击的时间延迟,给了MIDI系统时间来"重置"。但是在进一步的测试中,插入代码将"MUSIC_DONE"消息排队到主代码的时间延迟2秒,对问题没有任何影响。因此,这似乎可能与上下文切换有关,从我的主窗口切换到MessageBox窗口并返回,而不是任何时间延迟。

我发现一个网页,谈论发生在Windows 8上的MIDI系统的变化,这可能与它有关,除了我的MIDI播放代码似乎没有问题播放第一个文件,它只是拒绝播放任何后续的。该页位于:

http://coolsoft.altervista.org/en/blog/2013/03/what-happened-midi-mapper-windows-8

我也听到另一个开发者的游戏程序也有同样的问题:它会播放一次背景音乐,然后MIDI系统似乎打嗝。

那么,一个大问题:有没有人知道需要做些什么才能让Windows 10流畅地播放顺序MIDI文件?

首先,我强烈建议您使用win10系统进行调试,即使它是在VM中。因为win10是免费升级,所以这应该不是问题。

当你调用mciSendCommand(…, MCI_CLOSE,…),请在第三个参数中指定MCI_WAIT。0在这里是无效的。也许在win10上,这会导致你的代码没有等到Close完成。

最新更新