返回

PowerShell 设置麦克风音量至100% (附完整教程)

windows

PowerShell与命令行:设置麦克风音量至100%

问题分析

Windows 环境下,通过 PowerShell 或命令行程序化设置麦克风音量是音频控制中常见的需求。一些用户可能需要脚本自动调节音量以适应不同场景,但Windows提供的原生工具在这方面的支持略显不足。 传统的 WMI 方法以及某些 PowerShell 模块似乎并未直接提供操控麦克风音量的功能,这让直接操作麦克风音量变得挑战。 问题的根本在于如何找到可以直接访问和修改麦克风音量参数的接口。

解决方案一:利用 nircmd 工具

一个可行的方案是借助 NirSoft 提供的 nircmd.exe 工具。 这款小型命令行实用程序允许执行各种系统操作,包括调整音频设备的音量。 使用 nircmd 可以绕开 PowerShell 无法直接访问音频设备的限制,是一种简洁而高效的方法。

操作步骤:

  1. 下载 nircmd.exe: 从 NirSoft 官方网站下载 nircmd.exe 。 (搜索"NirCmd") 将下载后的程序放置在系统路径或你可以直接访问的路径中。
  2. 查找麦克风设备 ID: 使用如下 nircmd 命令列出系统中的录音设备,从中找到需要操作的麦克风设备对应编号。
nircmd.exe  monitorlist /saveseparate data.txt
type data.txt | findstr  "devName|capture|"

命令将输出所有设备的名称和状态。找到你想要控制的麦克风设备名(例如: devName:VoiceMeeter Output)和其capture:之后的编号(如 2), 这个编号将在后续步骤用到。
3. 使用 nircmd 设置音量: 替换上一步骤中找到的编号和目标音量。 setappvolume 需要音频设备的编号, 音量取值在0-65535之间, 65535 为100%, 将音量设为 100%。
batch nircmd.exe setappvolume "2" 65535
上面的代码假设上一步中获取到的设备编号为“2”,请将其替换成实际获取到的编号。 如果想要音量降为 50%则使用 32767 (65535 / 2) 代替。

安全性建议:
nircmd.exe 虽然功能强大,但需注意是从可靠的来源获取,以免下载到恶意程序。使用时建议指定完整路径。

解决方案二:结合 PowerShell 和 Core Audio API(进阶)

对于不需要依赖外部工具的场景,也可以考虑利用 Windows 提供的 Core Audio API 。 该方案需要稍微复杂的 PowerShell 代码。通过添加 C# 代码的方式, 使用Windows 内核级的 API ,能够获得最底层级的设备控制能力。 这是一个比较底层级的方案,可能在操作不同系统时遇到一些小差异,并且出错时不好排错。

操作步骤:

  1. PowerShell 加载必要的类 : 添加 C# 代码到 PowerShell 中, 其中包含对 COM 组件的调用和操作音频设备的相关接口定义。
Add-Type -TypeDefinition @"
    using System;
    using System.Runtime.InteropServices;

    [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IMMDevice
    {
        int Activate([Guid] ref Guid riid, uint dwClsCtx, IntPtr pActivationParams, out IntPtr ppInterface);
        int OpenPropertyStore(uint stgmAccess, out IntPtr ppProperties);
        int GetId(out IntPtr ppstrId);
        int GetState(out uint pdwState);
        int QueryInterface([Guid] ref Guid riid, out IntPtr ppvObject);
        int AddRef();
        int Release();

    }


    [Guid("A95309AF-F793-4017-A110-3C0C8D31D1E1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IMMDeviceCollection
    {
        int GetCount(out uint pcDevices);
        int Item(uint nDevice, out IntPtr ppDevice);
    }


    [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IMMDeviceEnumerator
    {
        int EnumAudioEndpoints(int dataflow, int stateMask, out IntPtr ppDevices);
        int GetDefaultAudioEndpoint(int dataflow, int role, out IntPtr ppEndpoint);
        int GetDevice(IntPtr pwstrId, out IntPtr ppDevice);
        int RegisterEndpointNotificationCallback(IntPtr pClient);
        int UnregisterEndpointNotificationCallback(IntPtr pClient);
    }

    [Guid("C02216F6-8C67-411B-9E14-3B14C1FF5D35"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IAudioEndpointVolume
    {
       int RegisterControlChangeNotify(IntPtr pNotify);
       int UnregisterControlChangeNotify(IntPtr pNotify);
       int GetChannelCount(out uint pnChannelCount);
       int GetLevelRange(IntPtr pfl, IntPtr pfr);
       int SetMasterVolumeLevel(float fLevelDB, Guid pguidEventContext);
       int SetMasterVolumeLevelScalar(float fLevel, Guid pguidEventContext);
       int GetMasterVolumeLevel(out float pfLevelDB);
       int GetMasterVolumeLevelScalar(out float pfLevel);
       int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMuted,  Guid pguidEventContext);
       int GetMute([MarshalAs(UnmanagedType.Bool)]out bool pbMuted);
       int GetVolumeStepInfo(IntPtr pnStep,IntPtr pnStepCount );
       int VolumeStepUp( Guid pguidEventContext );
       int VolumeStepDown( Guid pguidEventContext );
       int QueryHardwareSupport( IntPtr pdwHardwareSupport );
       int GetChannelVolumeLevel(uint nChannel, out float pfLevelDB);
       int SetChannelVolumeLevel(uint nChannel,float fLevelDB, Guid pguidEventContext);
        int GetChannelVolumeLevelScalar(uint nChannel,out float pfLevel);
         int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, Guid pguidEventContext);
    }


   public enum EDataFlow { eRender, eCapture, eAll }
   public enum ERole{ eConsole, eMultimedia, eCommunications, eAll }
   public static class  MMDeviceAPI
    {

       [DllImport("ole32.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern int CoCreateInstance([In] ref Guid rclsid,IntPtr pUnkOuter,[In] uint dwClsContext,[In] ref Guid riid,[Out] out IntPtr ppv);
       [DllImport("ole32.dll")]
        public static extern int CoInitialize(IntPtr pReserved);
       [DllImport("ole32.dll")]
       public static extern void CoUninitialize();
        [DllImport("kernel32.dll", SetLastError = true)]
         public static extern void CloseHandle(IntPtr handle);
          public static readonly Guid CLSID_MMDeviceEnumerator =new Guid(0xBCDE0395,0xE52F,0x467C,0x8E,0x3D,0xC4,0x57,0x92,0x91,0x69,0x2E);
          public static readonly Guid IID_IMMDeviceEnumerator = new Guid(0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x88, 0x2F, 0xA1, 0xC5, 0x6C);
           public static readonly Guid IID_IMMDeviceCollection =  new Guid(0x0BD7A1CB,0x86D3, 0x433F, 0x86, 0x0A, 0xDD, 0xA9, 0x10, 0x4E, 0x27, 0x26);
             public static readonly Guid IID_IMMDevice =  new Guid(0xD666063F, 0x1587, 0x4E43, 0x81, 0xF1, 0xB9, 0x48, 0xE8, 0x07, 0x36, 0x3F);
             public static readonly Guid IID_IAudioEndpointVolume =new Guid(0x5CDF2C82,0x841E, 0x4546, 0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x2E, 0xE1 );

          public static  float NormalizeVolume( float value,int maximum, int  mininum){ return  (float)((value-mininum)/ ((float) (maximum- mininum)));}
    }


    public static  class AudioDeviceController
   {
           public static bool SetDeviceVolume(  EDataFlow eFlow, ERole  eRole ,int targetVolume){

               // Init  COM library
             IntPtr res = IntPtr.Zero;
             try{

                 MMDeviceAPI.CoInitialize(IntPtr.Zero);
               IntPtr pDeviceEnumerator = IntPtr.Zero;
                IntPtr pDeviceCollection = IntPtr.Zero;


               Guid classId= MMDeviceAPI.CLSID_MMDeviceEnumerator;
                 Guid InterfaceId=MMDeviceAPI.IID_IMMDeviceEnumerator;

                int ret = MMDeviceAPI.CoCreateInstance( ref  classId, IntPtr.Zero,  0x1 ,ref InterfaceId,out   pDeviceEnumerator);

                   if(ret != 0 )return false; // failed to init mmDevice enmuerator;
                     InterfaceId =MMDeviceAPI.IID_IMMDeviceCollection;

                  //retrieve devices of specefic eflow  for example render or caputure ;

                  var enumerator  =   (IMMDeviceEnumerator)Marshal.GetObjectForIUnknown(pDeviceEnumerator);

                     if( enumerator.EnumAudioEndpoints(  (int)eFlow,0,out pDeviceCollection)!=0){return false;}


                 uint  count  ;
                 var col  =  (IMMDeviceCollection)Marshal.GetObjectForIUnknown(pDeviceCollection);
                      if (col.GetCount(out count)!=0)return false; // failed get device count


                       InterfaceId =MMDeviceAPI.IID_IMMDevice;


                      for( uint  i= 0;i<count ; i++){

                               IntPtr pDevice  ;

                           col.Item(i,out pDevice); // Get specific item
                         var currentDevice = (IMMDevice)Marshal.GetObjectForIUnknown(pDevice);



                              IntPtr  pProprerty  =IntPtr.Zero;

                         if(currentDevice.OpenPropertyStore(0 ,out pProprerty)==0){

                           if( pProprerty!=IntPtr.Zero){ // 获取音频设备音量管理对象


                         var deviceId = IntPtr.Zero;
                         if( currentDevice.GetId(out deviceId) == 0 ) {
                             if( deviceId!=IntPtr.Zero )   Marshal.FreeCoTaskMem(deviceId);

                         }



                              IntPtr    pVolumeInterface= IntPtr.Zero ;


                                  var    acvInterface  =MMDeviceAPI.IID_IAudioEndpointVolume;

                         if (  currentDevice.Activate(ref acvInterface,0 , IntPtr.Zero, out   pVolumeInterface)==0 ) {
                                         var endpointVolume = (IAudioEndpointVolume)Marshal.GetObjectForIUnknown(pVolumeInterface);
                                // float current;  endpointVolume.GetMasterVolumeLevelScalar(out  current); // 用于测试目的,显示当前音量;

                                   var normalizedVolume =MMDeviceAPI.NormalizeVolume( (float)targetVolume, 100,0 );

                                     endpointVolume.SetMasterVolumeLevelScalar( normalizedVolume ,Guid.Empty);
                                         Marshal.ReleaseComObject(endpointVolume);
                             }

                          Marshal.ReleaseComObject(Marshal.GetObjectForIUnknown(pProprerty));
                      }
                   }
                   Marshal.ReleaseComObject(currentDevice);


                               if(pDevice!= IntPtr.Zero )  Marshal.Release(pDevice);
                 }


                 Marshal.ReleaseComObject(col);

                      if (pDeviceEnumerator!=IntPtr.Zero){ Marshal.ReleaseComObject(Marshal.GetObjectForIUnknown(pDeviceEnumerator));}
             }finally{
                 MMDeviceAPI.CoUninitialize();
               }

              return true;
           }
 }
 "@
 # 使用示例
[AudioDeviceController]::SetDeviceVolume([AudioDeviceController+EDataFlow]::eCapture,  [AudioDeviceController+ERole]::eAll, 100)
  1. 理解代码逻辑: 代码利用 Core Audio API 的COM对象,实现从枚举设备,到激活控制接口,再到修改指定设备音量的过程。 这段代码能获取到指定类型的设备(输入设备eCapture,对应ERole::eAll的所有类型),然后通过 SetMasterVolumeLevelScalar 接口把麦克风音量设置为100%。 调整目标值即可设定不同大小的音量。

局限性: 此方案较为复杂,需要理解COM组件和 Windows 底层音频架构。 对操作系统的兼容性要求更高, 不同Windows版本的接口可能有所差异, 出现问题较难定位。

安全建议: 尽管使用了系统 API,代码也需仔细审核以确保其正确性和安全性。建议仅在确有需要的环境中使用该脚本,并谨慎处理其中的修改。

总结

针对通过 PowerShell 或命令行设置麦克风音量的需求,以上两种方法各有优势。 nircmd 简单直接,而基于Core Audio API的方法更为底层,能避免依赖第三方工具。 选择哪种方法,需要权衡其便利性和复杂性。 使用者应根据自身的技术能力和使用场景来决定适合自己的方案。 确保充分了解方案的工作原理及潜在的安全风险是明智之举。