Dot-Net

判斷windows目前是否正在播放聲音

  • August 8, 2017

所以我一直在思考這個問題一段時間,我無法弄清楚解決這個問題的正確方法是什麼。我想確定 Windows 是否在某個時間使用Powershell腳本輸出聲音。我可以確定音頻驅動程序是否有錯誤,但我無法確定係統是否正在播放聲音。

我查看了.NET類,System.Media裡面的三個類都與播放聲音或操縱系統聲音有關。

我不是要求為我編寫程式碼,我只需要知道從哪裡開始檢查 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-08return value > 0,當影片暫停時您將得到真實的。

下面是一個範例 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

您可以像任何外部 .exe 程序一樣在 PowerShell 中使用它。它將返回您可以使用標準方式檢索的錯誤級別,例如: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;
 }

引用自:https://stackoverflow.com/questions/45422255