如何在Windows计算机上编程播放声音

Windows操作系统中,通过编程方式控制声音播放设备可能并非易事。微软似乎有意为之,使得这一任务变得复杂。本文将提供一个解决方案,使用Managed C++(.NET) 来实现,但该方法也可以轻松地适应其他编程语言。

在深入讨论之前,需要明确几点:

由于微软不希望用户这样做,因此这个解决方案本质上是一种临时解决方案,一种变通方法。

任何第三方应用程序,如WindowsMedia Player,输出到声音播放音频设备时,都需要在音频设备更改后重新启动,因为它们通常在应用程序启动时只配置一次。自己的应用程序不必模仿这个限制。

只在XP上测试过这个解决方案。不知道为什么这个解决方案在Vista下不会工作,但在Vista下验证解决方案的可操作性留给读者作为练习。

因为在切换音频设备,而机器上实际可用的音频设备几乎肯定与机器不同,所以必须提供一些机器依赖的信息,以便解决方案在计算机上工作。

一般思路

通过编程方式启动“声音和音频设备属性”小程序,并使用消息系统操作面板。面板实际上会在屏幕上短暂出现,但这并不是问题。在看来,通过这种方式更改音频设备比直接修改注册表更好,因为注册表总是容易出错,因为从来没有确切清楚涉及哪些注册表键。

当然,这种一般方法可以扩展到操作任何小程序或应用程序,不仅限于“声音和音频设备属性”小程序。解决方案足够小,其适应其他小程序(.cpl)和应用程序(.exe)应该很明显。

使用代码

让开始吧。首先,需要确定想要切换的两个音频设备。从控制面板启动“声音和音频设备属性”小程序,转到“音频”选项卡,点击“声音播放”组合框。一个下拉列表显示了机器上可用于声音播放的音频输出设备。写下想要操作的两个设备(完全准确,所有的大写和小写字母以及标点都必须准确)。

在机器上,选择了“Realtek HD Audio output”和“CreamWare Play/Rec 1”。

在文件ACMDefines.h的顶部,用自己的选择替换选择:

#define AUDIO_DEVICE_1 _T("Realtek HD Audio output") #define AUDIO_DEVICE_2 _T("CreamWare Play/Rec 1")

现在,构建解决方案,它应该在机器上工作。

解释发生了什么

从托管代码启动小程序(.cpl)很容易,它是一行代码:

Process::Start("rundll32.exe", "shell32.dll,Control_RunDLL mmsys.cpl");

小程序“mmsys.cpl”位于system32目录中,与其他控制面板中的小程序一起,没有什么奇怪的。故意没有立即启动显示“音频”选项卡的小程序,以便在示例代码中演示如何以编程方式选择选项卡。但是,可以将上述行更改为:

Process::Start("rundll32.exe", "shell32.dll,Control_RunDLL mmsys.cpl,,2");

结果将是小程序窗口出现,已经选择了“音频”选项卡,因此不需要在代码中执行选择所需选项卡的过程。但是,如果想利用解决方案作为一个模板,适应自己的具体需求,最好展示所有涉及的步骤。

一旦小程序成功启动,获取小程序窗口的句柄:

appletWindow = FindWindow(NULL, APPLET_WINDOW_TITLE);

从那里,它只是一个向小程序发送消息的问题,模拟键盘活动。概念很简单。困难在于获取小程序窗口中各种控件的句柄。首先,需要在小程序窗口中使用Spy++(在Visual Studio内部)来发现想要操作的控件的类型和标签。所以,启动小程序,打开Spy++,查找小程序窗口。应该看到类似以下内容:

Window xxxxxxxx "Sounds and Audio Devices Properties" #xxxxx (Dialog)

点击这个条目左侧的“+”以展开子窗口列表。其中一个子条目是'Window xxxxxxxx "&Apply" Button'。这里有两个重要的信息。一,它是一个类型的控件BUTTON。二,按钮的文本(或标签)是"&Apply"(注意实际GUI中不出现的和号)。

在示例代码中,使用调用findChildWindow()来获取这个控件(以及所有后续控件)的句柄:

applyButton = findChildWindow(appletWindow, BUTTON, APPLY_BUTTON);

BUTTON在ACMDefines.h中定义为:

#define BUTTON _T("Button")

并且APPLY_BUTTON定义为:

#define APPLY_BUTTON _T("&Apply")

现在,有了“Apply”按钮的句柄。接下来需要做的是选择“Audio”选项卡。在Spy++中,会看到一个条目,如:

Window xxxxxxxx '"' SysTabControl32

类型是SysTabControl,但没有标签。它没有标签不是问题,因为在小程序窗口中只有一个类型的控件"SysTabControl32"。以与获取“Apply”按钮句柄相同的方式获取这个控件的句柄:

tabControl = findChildWindow(appletWindow, SYS_TAB_CTRL, NULL);

一旦有了句柄,模拟键盘活动(ctrl-up/down/right/left)来选择“Audio”选项卡:

SendMessage(tabControl, WM_KEYDOWN, VK_CONTROL, 0); //ctrl key down SendMessage(tabControl, WM_KEYDOWN, VK_RIGHT, 0); //right arrow key down SendMessage(tabControl, WM_KEYUP, VK_RIGHT, 0); //right arrow key up SendMessage(tabControl, WM_KEYDOWN, VK_RIGHT, 0); //right arrow key down SendMessage(tabControl, WM_KEYUP, VK_RIGHT, 0); //right arrow key up SendMessage(tabControl, WM_KEYUP, VK_CONTROL, 0); //ctrl key up

一旦显示了音频选项卡,查找想要的音频设备选择的句柄(AUDIO_DEVICE_1):

comboBox = findChildWindow(appletWindow, COMBO_BOX, AUDIO_DEVICE_1);

如果找不到句柄,查找AUDIO_DEVICE_2的句柄。示例代码假设这两个音频设备中的一个总是被选中,因此总是组合框中选定的项目。如果机器有超过两个音频设备,并且选择的两个设备都不是默认选定的项目,那么必须首先查找默认选定项目的句柄,然后从那里选择两个感兴趣的设备之一。示例代码没有这样做,它假设两个设备中的一个已经被选中。

一旦有了当前选定项目在组合框中的句柄,只需向它发送一个消息以更改为新的音频设备

SendMessage(comboBox, CB_SELECTSTRING, (WPARAM)NONE, (LPARAM)&AUDIO_DEVICE_2);

然后,向“Apply”按钮发送消息以应用更改:

SendMessage(applyButton, BM_CLICK, 0, 0); //apply the change

并关闭小程序窗口:

SendMessage(appletWindow, WM_CLOSE, 0, 0); //close the applet

有趣的地方

如果想启动一个常规应用程序(.exe)并使用模拟按键来操作它,有几种方法可以做到这一点。这里只是一个可能性:

protected: bool launchApplication() { HINSTANCE returnCode; returnCode = ShellExecute((HWND)errorBox->getWindowHandle(), //window "open", //verb - action "C:\\Program Files\\SomeProgram.exe", //program file specification "", //parameters (none) "C:\\Program Files", //directory where opened SW_SHOWMINNOACTIVE); //window minimized, no focus return ((returnCode > (HINSTANCE)32 && returnCode != (HINSTANCE)SE_ERR_NOASSOC) ? true : false); }

对于.exe文件,只有启动方法不同,其他一切都与.cpl文件相同。

正如看到的,概念非常简单。这个编码示例的价值在于它展示了一种紧凑和通用的方法,用于从托管C++获取小程序中各种控件的句柄。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485