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

Posted

技术标签:

【中文标题】为啥 MIDI 音序器无法在 Windows 10 上第二次播放【英文标题】:Why does MIDI sequencer fail to play second time on Windows 10为什么 MIDI 音序器无法在 Windows 10 上第二次播放 【发布时间】:2015-08-03 21:48:23 【问题描述】:

多年来,我一直使用 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_SUCCESSFUL 时,它将通知我的主代码音乐完成,然后该主代码最终将再次调用(如果它愿意)到 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 调用以显示正在接收的通知。使用 Win 10 的用户看到的消息与我在早期版本的 Windows 中看到的消息完全相同:当 SUCCESSFUL 发生时 wParam=1 和 lParam=1,当 ABORTED 发生时 wParam=4 和 lParam=1。但关键在于:此时 MessageBox 发生,然后当用户在 MessageBox 上单击 OK 时,MIDI 文件重新启动就好了!我的第一个想法是 MessageBox 出现并被点击的时间延迟使 MIDI 系统有时间“重置”。但是进一步的测试,插入代码以延迟 2 秒将“MUSIC_DONE”消息排队到我的主代码对问题没有影响。所以看起来这可能与从我的主窗口到 MessageBox 窗口的上下文切换有关,而不是任何时间延迟。

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

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

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

那么,一个大问题:有谁知道需要做什么才能让 Windows 10 顺利播放连续的 MIDI 文件?

【问题讨论】:

【参考方案1】:

首先,我强烈建议您使用 Win 10 系统进行调试,即使它位于 VM 中。由于 Win 10 是免费升级的,这应该不是问题。

当您调用 mciSendCommand(..., MCI_CLOSE, ...) 时,请在第三个参数中指定 MCI_WAIT。 0 在这里无效。也许在 Win 10 上,这会导致您的代码不等到关闭完成。

【讨论】:

我正在使用 Win 10 系统。同时,我制作了 MCI_WAIT 补丁(感谢您的指点!),但它(根据一位测试人员的结果)似乎没有任何效果。 MCI_PLAY 命令仍然返回 MCIERR_SEQ_NOMIDIPRESENT 错误。 无赖。嗯,关于 MessageBox 的事情是它导致消息被抽出。睡觉没有。因此,如果您说 MessageBox 已修复它,则代码中的某些内容可能会占用您的线程,例如MCI_等待?在 Win 10 上的调试器中运行代码后可能很容易看到。 我从未找到解决此问题的方法,并认为这是 Win 10 中的一个错误。此外,该错误仅(大部分?)出现在从 Win 8.1 升级到 Win 10 的系统上。可能是一个完整的红鲱鱼,因为我的数据集在这一点上很小,但这是一种可能性。我通过从上面相当简单、容易的代码切换到更复杂的“流”MIDI 接口来避免这个问题。幸运的是,一个朋友有一个“准备好加入”流式 MIDI 代码库,否则我自己会浪费一个月或更长时间来弄清楚它。谢谢,微软。无所事事。 看起来 MCI 大部分都有效,但还没有完全没有错误since DirectX。大约十年前,我的一个基于 MCI 的应用程序在安装 Windows Media Player 后停止工作。尽管如此,如果您的应用程序只是通过显示一个消息框来工作,这会导致消息被抽出,那么您的代码中可能有一些可以很容易地修复的东西。很高兴您找到了解决方法。 问题依然存在,即使在流媒体界面。进一步的提示使它看起来像是一个内存分段错误,因为 MIDI 文件正在被馈送数据片段,并且可能是 32 位与 64 位寻址。如果流版本没有处理正确的寻址,则可能有问题,但高级 MCI 版本......仍然指向 Windows 代码内部的问题(也可能是类似的寻址问题)。但看起来某些内存可能会在某个时候被丢弃,从而确定是否会出现以及出现哪种问题。

以上是关于为啥 MIDI 音序器无法在 Windows 10 上第二次播放的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中播放(MIDI)序列中的音频剪辑?

可编程“实时”MIDI处理

Java MIDI 音序器永无止境

每次调用脚本时在文件的下一行返回值。 MIDI 命令行音序器

MIDI 通道轨道相关

如何获取对非默认 MIDI 音序器的引用?