确定窗口是否正在播放声音

所以我一直在思考这个问题一段时间,我不知道什么是正确的方式去做这件事。 我想确定Windows是否在某个时间使用Powershell脚本输出声音。 我可以确定audio驱动程序是否有错误,但我不能确定系统是否在播放声音。

我看了一下System.Media.NET类,里面的三个类都和播放声音或操作系统声音有关。

我不是要求为我写的代码,我只需要知道从哪里开始检查Windows系统是否正在播放声音。

我有一个声音监视器,它不断监视Node.js平台上的声音,当它失去声音时,它会发送给我一个文本。 那么,我也希望它能够通过它所连接的所有系统,看看故障在哪里。 这就是为什么我想看看Windows电脑是否在播放声音。

以下是如何使用Simon Mourier提供的代码。

运行下面的代码:

 Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; namespace Foo { public class Bar { public static bool IsWindowsPlayingSound() { IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); IMMDevice speakers = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia); IAudioMeterInformation meter = (IAudioMeterInformation)speakers.Activate(typeof(IAudioMeterInformation).GUID, 0, IntPtr.Zero); float value = meter.GetPeakValue(); // this is a bit tricky. 0 is the official "no sound" value // but for example, if you open a video and plays/stops with it (w/o killing the app/window/stream), // the value will not be zero, but something really small (around 1E-09) // so, depending on your context, it is up to you to decide // if you want to test for 0 or for a small value return value > 1E-08; } [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] private class MMDeviceEnumerator { } private enum EDataFlow { eRender, eCapture, eAll, } private enum ERole { eConsole, eMultimedia, eCommunications, } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")] private interface IMMDeviceEnumerator { void NotNeeded(); IMMDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role); // the rest is not defined/needed } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D666063F-1587-4E43-81F1-B948E807363F")] private interface IMMDevice { [return: MarshalAs(UnmanagedType.IUnknown)] object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid iid, int dwClsCtx, IntPtr pActivationParams); // the rest is not defined/needed } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")] private interface IAudioMeterInformation { float GetPeakValue(); // the rest is not defined/needed } } } '@ 

我替换了所有的var类型,因为这似乎解决了在PowerShell版本2上不编译代码的问题。

一旦加载,你可以像这样检查状态:

 [Foo.Bar]::IsWindowsPlayingSound() True or False 

我已经测试过这个与PowerShell 5.1上的Windows 10 1703的工作


但有一些警告:

 this is a bit tricky. 0 is the official "no sound" value but for example, if you open a video and plays/stops with it (w/o killing the app/window/stream), the value will not be zero, but something really small (around 1E-09) so, depending on your context, it is up to you to decide if you want to test for 0 or for a small value 

因此,如果将return value > 1E-08更改为return value > 0 ,则在视频暂停时会变为true。

下面是一个示例C#代码,用于确定Windows是否正在渲染任何音频流。 它使用Windows Core Audio API(特别是IAudioMeterInformation接口 ),并且在Vista和更高版本上受支持。

 public static bool IsWindowsPlayingSound() { var enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); var speakers = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia); var meter = (IAudioMeterInformation)speakers.Activate(typeof(IAudioMeterInformation).GUID, 0, IntPtr.Zero); var value = meter.GetPeakValue(); // this is a bit tricky. 0 is the official "no sound" value // but for example, if you open a video and plays/stops with it (w/o killing the app/window/stream), // the value will not be zero, but something really small (around 1E-09) // so, depending on your context, it is up to you to decide // if you want to test for 0 or for a small value return value > 1E-08; } [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] private class MMDeviceEnumerator { } private enum EDataFlow { eRender, eCapture, eAll, } private enum ERole { eConsole, eMultimedia, eCommunications, } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")] private interface IMMDeviceEnumerator { void NotNeeded(); IMMDevice GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role); // the rest is not defined/needed } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D666063F-1587-4E43-81F1-B948E807363F")] private interface IMMDevice { [return: MarshalAs(UnmanagedType.IUnknown)] object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid iid, int dwClsCtx, IntPtr pActivationParams); // the rest is not defined/needed } [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")] private interface IAudioMeterInformation { float GetPeakValue(); // the rest is not defined/needed } 

正如我的评论所说,我还创建了一个开源的c + +项目,一个简单的无摩擦零依赖关系控制台应用程序,可在这里: https : //github.com/smourier/IsWindowsPlayingSound 。 我已经添加了一个应该支持32位和64位操作系统的x86版本二进制文件: https : //github.com/smourier/IsWindowsPlayingSound/releases

你可以在PowerShell中像使用任何外部的.exe程序一样使用它。 它将返回一个可以使用标准方式检索的错误级别,例如: https : //blogs.msdn.microsoft.com/powershell/2006/09/15/errorlevel-equivalent/

这是等效的C ++代码:

  #include "stdafx.h" // includes <Endpointvolume.h> and <Mmdeviceapi.h> #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) #define __WFILE__ WIDEN(__FILE__) #define HRCHECK(__expr) {hr=(__expr);if(FAILED(hr)){wprintf(L"FAILURE 0x%08X (%i)\n\tline: %u file: '%s'\n\texpr: '" WIDEN(#__expr) L"'\n",hr, hr, __LINE__,__WFILE__);goto cleanup;}} #define RELEASE(__p) {if(__p!=nullptr){__p->Release();__p=nullptr;}} int main(int argc, char *argv[]) { BOOL playing = FALSE; BOOL loopmode = FALSE; float epsilon = 1E-07; float value = 0; HRESULT hr = S_OK; IMMDeviceEnumerator* pEnumerator = NULL; IMMDevice *pDevice = NULL; IAudioMeterInformation *pMeter = NULL; // Parse optional args // "loop" -> sets a loop mode for easy testing // <float value> -> changes epsilon for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "loop")) { loopmode = TRUE; continue; } float eps = atof(argv[i]); if (eps != 0.0) { epsilon = eps; continue; } } CoInitialize(NULL); HRCHECK(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator)); HRCHECK(pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &pDevice)); HRCHECK(pDevice->Activate(__uuidof(IAudioMeterInformation), CLSCTX_ALL, NULL, (void**)&pMeter)); do { HRCHECK(pMeter->GetPeakValue(&value)); playing = value > epsilon; if (!loopmode) break; printf("%.10f playing:%i\n", value, playing); Sleep(100); } while (TRUE); cleanup: RELEASE(pMeter); RELEASE(pDevice); RELEASE(pEnumerator); CoUninitialize(); if (FAILED(hr)) { printf("An error occurred: 0x%08X\n", hr); return hr; } if (playing) { printf("Windows is playing a sound.\n"); } else { printf("Windows is not playing a sound.\n"); } return playing; } 

你可以使用Chris Hunt编写的AudioDeviceCmdlets模块

Write-DefaultAudioDeviceValue -StreamValue看起来像你在找什么。 否则,你可以看看他如何使用CoreAudioApi来拉取这些值