12

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 certain time using a Powershell script. I can determine whether or not the audio driver has an error, but I cannot for the life of me figure out if the system is playing sound.

I looked at the .NET class for System.Media and the three classes inside all had to do with playing sound or manipulating the system sounds.

I'm not asking for code to be written for me, I just need to know where to start to check if the windows system is currently playing sound.

I have a sound monitor that is continuously monitoring sound on the Node.js platform and when it loses sound it sends me a text. Well, I also want it to go through all the systems its hooked up to and see where the fault lies. That's why I want to see whether or not the windows computer is playing sound.

JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49
Adam McGurk
  • 186
  • 1
  • 19
  • 54

3 Answers3

16

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;
  }
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • So I've never worked with native C# before (just using some parts of it while I run Powershell), but the issue is, whenever I try to install the .NET framework for running C#, it freaks my computers out around here. Meaning, I can't run powershell on them (it won't even come up) and when I try to install visual studio after installing the .NET framework it says: "Bootstrapper stopped working" – Adam McGurk Aug 03 '17 at 13:47
  • OK, so as you can obviously tell, I'm pretty ignorant when it comes to this type of stuff, but I finally got to where I'm trying to compile the code with csc.exe and I'm getting the following errors:`(1:15) Expected class, delegate, enum, interface, or struct` `(3:48) Expected class, delegate, enum, interface, or struct` `(14:1) Type or namespace definition, or end-of-file expected` – Adam McGurk Aug 03 '17 at 15:43
  • 1
    Hmmm. you seems a bit too far from C# knowledge (no hard feelings :-). What I can do is create a C++ project on github with a smilar code (in C++) and an exe that you can use from powershell (I guess powershell can easily use an exe return code). This way you won't have any .net framework dependency issue either, but you'll have to ship an exe – Simon Mourier Aug 03 '17 at 17:53
  • 3
    Another solution is to add C# compiled on the fly to powershell with Add-Type https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/25/use-powershell-to-interact-with-the-windows-api-part-1/ never tried it though... – Simon Mourier Aug 03 '17 at 17:55
  • no hard feelings at all, it's true! I've been meaning to learn C#, but I have not yet, so much of the C#/.NET stuff goes over my head. That would be amazing of you! – Adam McGurk Aug 03 '17 at 17:56
  • So those compilation errors weren't errors in the code? – Adam McGurk Aug 03 '17 at 17:57
  • @AdamMcGurk - I have updated my answer to avoid dependencies difficulties. – Simon Mourier Aug 04 '17 at 07:30
  • I don't even know what to say....you went to so much trouble, thank you! The only thing is, I have sound playing on my PC and it registers that Windows is not playing a sound – Adam McGurk Aug 05 '17 at 19:42
  • I'm tempted to just give you the answer credit though because of how hard you worked and because it doesn't look like it's possible with my tech – Adam McGurk Aug 05 '17 at 19:42
  • The (C#) code works pretty darn well. I've played sound and silence with WMP and VLC (as an external player) and only when non-silence sound was played the result of the method was true. Nice. – wp78de Aug 05 '17 at 20:41
  • I also wish I could give the answer to both of you, man you guys did so much! – Adam McGurk Aug 07 '17 at 15:24
  • Excellent Answer. But What about Microphones? When i use the microphone, IsWindowsPlayingSound returns false – Ali123 Oct 16 '19 at 07:37
  • @Ali123 - IsWindowPlayingSound returns true if sound is rendered on an output. That has nothing to do with inputs. – Simon Mourier Oct 16 '19 at 08:12
14

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.

Persistent13
  • 1,522
  • 11
  • 20
  • So I just tested this code and I got two of the following errors: 1. It's still not compiling correctly. The compiliations errors are in a comment to @Simon Mourier answer. 2. I got the following error four times: `Add-Type C:\path\to\j2zoomon.0.cs The type or namespace name 'var' could not be found` – Adam McGurk Aug 03 '17 at 20:27
  • I also want to say I understand if this is too much to do for just 50 rep. I'm a web developer and this is part of a web app that I'm developing for a radio station, and I've been struggling with this problem for about 3 months, so thank you for all of the research you all have done – Adam McGurk Aug 03 '17 at 20:28
  • 1
    What version of PowerShell and OS are you on? You can check with $PSVersionTable.PSVersion.ToString() and gwmi win32_operatingsystem | select Version – Persistent13 Aug 03 '17 at 20:31
  • My powershell version is 2.0 and I'm running Windows 7. That's what's kept me so hamstrung this whole project, there are so many things I wish I could do if I were allowed to upgrade Powershell Versions, but I'm doing this for a university which has draconian policies. – Adam McGurk Aug 03 '17 at 20:32
  • 2
    Thanks Adam. I assume with the draconian policies you do not have local admin but in the event that you do please see the updated answer. – Persistent13 Aug 03 '17 at 20:42
  • So odd...there is probably something more at play here and unfortunately I am the only in house developer (a web developer by the way, not even a sys admin or desktop developer although I used to think I was pretty versatile. Apparently not). I am getting the same errors even after adding the config file to to that path. – Adam McGurk Aug 03 '17 at 20:53
  • One last shot, if the new solution doesn't work I'm out of ideas. – Persistent13 Aug 06 '17 at 22:36
  • HOLY COW!!!!! It works! It compiles and it detects the sound and it runs on Powershell v2 with Windows 7....the issue gets into how you have to set different things as default playback devices in a radio environment, and a few other issues, but this works, I can't thank you enough for all of your hard work!! – Adam McGurk Aug 07 '17 at 15:24
  • 1
    @HappyBirthdayDelphi_25 It's C# code that is complied using PowerShell's `Add-Type` cmdlet. – Persistent13 Feb 15 '20 at 17:12
4

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

BenH
  • 9,766
  • 1
  • 22
  • 35
  • I remember seeing that in my research a month or so ago, and I remember now why I wasn't able to use it. The unblock-file cmdlet isn't available in powershell v2 – Adam McGurk Aug 03 '17 at 13:45
  • 2
    @AdamMcGurk You can unblock files in Windows Explorer by right-clicking and select "Properties". There should be a checkbox at the bottom of the first tab. But upgrading past PowerShell 2 would also be a really good idea. So many better tools in the newer versions. – BenH Aug 03 '17 at 13:47
  • I keep remembering things haha...I remember doing that, but I get the `This assembly is built by a runtime newer than the currently loaded runtime...` error. And man, I would love to upgrade past Powershell v2, but for some reason, anytime I upgrade the .NET framework on any of the computers in the shop, it renders Powershell unusable – Adam McGurk Aug 03 '17 at 13:55