确定 windows 当前是否正在播放声音
Determine if windows is currently playing sound
所以我一直在思考这个问题一段时间,但我想不出解决这个问题的正确方法是什么。我想使用 Powershell
脚本确定 Windows 是否在特定时间输出声音。我可以确定音频驱动程序是否有错误,但我无法确定系统是否正在播放声音。
我查看了 .NET
class System.Media
里面的三个 class 都与播放声音或操纵系统声音有关。
我不是要为我编写代码,我只需要知道从哪里开始检查 windows 系统当前是否正在播放声音。
我有一个声音监视器,它会持续监视 Node.js 平台上的声音,当它失去声音时,它会向我发送一条文本。好吧,我还希望它遍历它所连接的所有系统并查看故障所在。这就是为什么我想看看 windows 电脑是否在播放声音。
你可以使用 AudioDeviceCmdlets
module written by Chris Hunt
Write-DefaultAudioDeviceValue -StreamValue
看起来像您要找的东西。否则,您可以查看他的来源,了解他如何使用 CoreAudioApi
提取这些值
这是一个示例 C# 代码,用于确定 Windows 是否正在呈现任何音频流。它使用 Windows Core Audio API(特别是 IAudioMeterInformation interface)并在 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
您可以像使用任何外部 .exe 程序一样在 PowerShell 中使用它。它将 return 一个您可以使用标准方法检索的错误级别,例如: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;
}
以下是如何使用 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。
所以我一直在思考这个问题一段时间,但我想不出解决这个问题的正确方法是什么。我想使用 Powershell
脚本确定 Windows 是否在特定时间输出声音。我可以确定音频驱动程序是否有错误,但我无法确定系统是否正在播放声音。
我查看了 .NET
class System.Media
里面的三个 class 都与播放声音或操纵系统声音有关。
我不是要为我编写代码,我只需要知道从哪里开始检查 windows 系统当前是否正在播放声音。
我有一个声音监视器,它会持续监视 Node.js 平台上的声音,当它失去声音时,它会向我发送一条文本。好吧,我还希望它遍历它所连接的所有系统并查看故障所在。这就是为什么我想看看 windows 电脑是否在播放声音。
你可以使用 AudioDeviceCmdlets
module written by Chris Hunt
Write-DefaultAudioDeviceValue -StreamValue
看起来像您要找的东西。否则,您可以查看他的来源,了解他如何使用 CoreAudioApi
这是一个示例 C# 代码,用于确定 Windows 是否正在呈现任何音频流。它使用 Windows Core Audio API(特别是 IAudioMeterInformation interface)并在 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
您可以像使用任何外部 .exe 程序一样在 PowerShell 中使用它。它将 return 一个您可以使用标准方法检索的错误级别,例如: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;
}
以下是如何使用 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。