使用PowerShell脚本切换Windows7默认音频设备

2014-12-07 16:30 by hackerzhou

有两年没写博客了,究其原因估计懒惰的成分比较大一些。今年发生的一些事打乱了我原本计划好的人生,在转折点上我还是选择重新做回追求生活品质的技术男。相信终会有和我三观match&志同道合的有缘人出现,理解并支持我表达爱的方式,相互扶持着走漫漫人生路。

言归正传,本文笔者想分享的解决方案是想通过脚本来实现一键切换音频输出设备。随着大家对于音频设备的要求越来越高,一台计算机上往往接了不止一台音频输出设备(比如笔者这边就在3.5mm接口上接了一个Momentum,光纤接口上接了一个Philips HTL2160蓝牙声吧),那么如何简便地切换这些设备就成了一个问题。通常的做法需要在播放设备里手动把特定输出端标志成默认设备才行,无法避免的几次鼠标点击以及菜单选择显得并不那么方便。所以笔者就想,如果能用脚本来实现切换应该会很方便,而且甚至可以配合键盘的自定义按键来实现一键切换。

查了若干资料,并没有发现有现成的API和注册表配置项可以调用(XP下似乎有一个Sound Mapper的注册表可以直接改目的设备的index)。不过出于习惯还是觉得这类配置属性应该是写在注册表HKEY_LOCAL_MACHINE或者HKEY_CURRENT_USER下的,仔细思索了下音量应该是全局共享的属性所以应该在HKEY_LOCAL_MACHINE下。于是开启注册表比对工具在手动设置前和设置后比对了下注册表,终于找到了那个键值路径:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\{DEVICE_GUID}下的Role:0、Role:1和Role:2三个属性在设置之后会发生变化。以为找到方法了?Naive!微软才不会那么轻松就让我们找到隐藏关卡呢~

让我们先来看看这个注册表节点的属性:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\{158b3b6c-02c6-4c24-8900-2448fc78efd3}]
"DeviceState"=dword:00000001
"Role:0"=hex:de,07,0c,00,00,00,07,00,06,00,1b,00,29,00,c5,02
"Role:1"=hex:de,07,0c,00,00,00,07,00,06,00,1b,00,29,00,c5,02
"Role:2"=hex:de,07,0c,00,00,00,07,00,06,00,1b,00,29,00,c5,02

坑爹的二进制类型的值呐,而且每次手动更改之后值都与之前不一样。等等,每次更改都会变的值如果不是伪随机数/GUID那就是————时间了!让我们先来解读下这个16字节的二进制数据。顺便普及下基础编程知识,每个0x00这样的数字都代表了一个十六进制数字,称为1个byte;两个byte是short;四个byte是int。而且除了少部分操作系统之外我们用的大都是小端法(little-endian)来存储二进制数据,即低位排在最前。
那么这个二进制可以被解读为:

0x07DE = 2014 (dec) //Year
0x000C = 12   (dec) //Month
0x0000 = 0    (dec) //DayOfWeek
0x0007 = 7    (dec) //Day
0x0006 = 6    (dec) //Hour
0x001B = 27   (dec) //Minute
0x0029 = 41   (dec) //Second
0x02C5 = 709  (dec) //Millisecond

看看我们发现了什么~看起来是个日期+时间,根据和当前时间的比对得出用的是UTC时间。第三个short是0不太好判断,一开始只是猜测是DayOfWeek,不过可以通过更改系统日期来做个快速的确认。鉴于我们解读出来的数据,可以做个大胆的推测:Windows自带的控制程序把当前系统时间写进不同输出设备下的Role:[0-2]属性,拥有最近的时间戳的就拥有了默认优先权。又做了次实验,似乎Role:0以及Role:1是用来作为默认设备,而Role:2是作为默认通信设备。至此,似乎解决方案已经呼之欲出了。祭出Windows下的脚本语言PowerShell来完成开发,因为PowerShell可以用脚本语言的方式直接调用.NET的API比较方便些。

写完之后一运行,出现错误“Set-ItemProperty : 不允许所请求的注册表访问权。”这是什么鬼,搜了一下发现是不符合注册表的ACL的缘故,简单的来说就是注册表里的节点都是有权限限制的,谁可以读谁可以写都能单独限制。这时候就要用第二个workaround了:右击Device GUID那个节点,选择权限。
switch_device1 switch_device2
发现在这个页面上并不能给Administrators组或者当前管理员帐号分配系统权限,是因为Windows系统还有一个奇葩的内建帐号SYSTEM存在,进入“高级”把所有者从SYSTEM手里抢回来就可以了。给当前用户或者用户组赋了权限之后别忘把SYSTEM也给加上,因为SYSTEM现在不是Owner了,不过不加有没有side effect笔者也没有进一步尝试。

至此脚本终于可以顺利执行了,但似乎还有个小问题,改了注册表之后在界面上可以看到默认设备变了,但实际音频并没有做切换。想想也是,注册表只是持久化下来的配置,我们一般写程序的时候需要维护一个in-memory的状态值,但我们这是在进程外修改,在不知道hook和如何触发回调函数的时候最简单粗暴的方法就是restart那个进程让它重新初始化。果然,在重启了audiosrv服务之后音频果然切换了~大功告成!

依照惯例贴下实现的代码:

#
# Script to switch default audio device on Windows 7
# Author: hackerzhou
# Date:   2014/12/07
#

if ($args.Length -ne 1) {
  Write-Host "Usage: powershell.exe -File SwitchAudioDevice.ps1 {DEVICE GUID}"
  Exit 1
}

$device = $args[0]
function WriteDateBinary($dateBinary, $value, $index) {
  $shortValue = [UInt16]$value
  $tmp = [BitConverter]::GetBytes($shortValue)
  [Array]::Copy($tmp, 0, $dateBinary, $index, 2)
}

$currentUtcDateTime = (Get-Date).ToUniversalTime()
$dateBinary = New-Object Byte[] 16
WriteDateBinary $dateBinary $currentUtcDateTime.Year         0
WriteDateBinary $dateBinary $currentUtcDateTime.Month        2
WriteDateBinary $dateBinary $currentUtcDateTime.DayOfWeek    4
WriteDateBinary $dateBinary $currentUtcDateTime.Day          6
WriteDateBinary $dateBinary $currentUtcDateTime.Hour         8
WriteDateBinary $dateBinary $currentUtcDateTime.Minute      10
WriteDateBinary $dateBinary $currentUtcDateTime.Second      12
WriteDateBinary $dateBinary $currentUtcDateTime.Millisecond 14

$regPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render\$device"
Set-ItemProperty $regPath "Role:0" $dateBinary -type Binary
Set-ItemProperty $regPath "Role:1" $dateBinary -type Binary
Set-ItemProperty $regPath "Role:2" $dateBinary -type Binary

Restart-Service audiosrv

最后,晒下我现在的多媒体工作环境,Windows台式机/MacBook Pro/Dell 2209WA/Dell U2413/Philips HTL2160/Momentum/AIX RS-8A/iCON MicU/11TB NAS(HP MicroServer Gen8)。低音炮占地方而且碍眼后来被我移到显示器背后了,反正人耳对低音来源方位不敏感。
DSC04023 DSC04031

本文基于 署名 2.5 中国大陆 许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 hackerzhou 并包含 原文链接
发表评论

本文有 2 条评论

  1. papu
    2015-02-16 21:52

    无意中看到了这个网站……你好浪漫啊!祝幸福!http://bingu.pw

  2. luguozmy
    2015-01-31 05:22

    怎么, Minyue离开你了?

发表评论