Determine if windows is currently playing sound

后端 未结 3 1043
谎友^
谎友^ 2020-12-14 23:58

So I\'ve been pondering on this problem for a while and I can\'t figure out what the right way to go about this is. I want to determine if Windows is outputting sound at a c

相关标签:
3条回答
  • 2020-12-15 00:37

    You could use AudioDeviceCmdlets module written by Chris Hunt

    Write-DefaultAudioDeviceValue -StreamValue looks like what you are looking for. Otherwise you can take a look at his source on how he pulling those values using the CoreAudioApi

    0 讨论(0)
  • 2020-12-15 00:41

    Here is a sample C# code that determines if Windows is rendering any audio stream. It uses Windows Core Audio API (specifically the IAudioMeterInformation interface) and is supported on Vista and higher.

    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
    }
    

    As said in my comment, I have also created an open source c++ project, a simple friction-free zero-dependencies console application, available here: https://github.com/smourier/IsWindowsPlayingSound. I have added one x86 release binary that should support 32 and 64 bit OSes: https://github.com/smourier/IsWindowsPlayingSound/releases

    You can use it in PowerShell like any external .exe program. It will return an error level that you can retrieve using standard ways, for example: https://blogs.msdn.microsoft.com/powershell/2006/09/15/errorlevel-equivalent/

    Here is the equivalent C++ code:

      #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;
      }
    
    0 讨论(0)
  • 2020-12-15 00:53

    Here's how to use the code that Simon Mourier provided.

    Run the code below:

    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
            }
        }
    }
    '@
    

    I replaced all var types as that seems to fix the issue with the code not compiling on PowerShell version 2.

    Once loaded you can check the state like so:

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

    I've tested this working with Windows 10 1703 on PowerShell 5.1


    But there are caveats:

    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
    

    So if you change return value > 1E-08 to return value > 0 you will get true when a video is paused.

    0 讨论(0)
提交回复
热议问题