【问题标题】:Determine if windows is currently playing sound判断windows当前是否正在播放声音
【发布时间】:2018-01-07 09:38:28
【问题描述】:

所以我一直在思考这个问题一段时间,但我无法弄清楚解决这个问题的正确方法是什么。我想确定 Windows 是否使用Powershell 脚本在某个时间输出声音。我可以确定音频驱动程序是否有错误,但我无法确定系统是否正在播放声音。

我查看了System.Media.NET 类,里面的三个类都与播放声音或操纵系统声音有关。

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

我有一个声音监视器,它在 Node.js 平台上持续监控声音,当它失去声音时,它会向我发送一条文本。好吧,我还希望它检查它所连接的所有系统,看看故障在哪里。这就是为什么我想看看windows电脑是否在播放声音。

【问题讨论】:

  • 我发现这个问题基本相同,虽然从未正式回答:stackoverflow.com/questions/17113179/…
  • @TheIncorrigible1 我实际上提到了很多,它帮助我检测音频驱动程序问题,但实际上并没有播放音频
  • 您还应该说明您打算如何处理这些信息。 (如果只是为了满足你的好奇心,请别人为你做一堆研究是没有意义的。)
  • @Bill_Stewart 已编辑
  • 你可以接受一个小的(无依赖)c#示例代码吗?

标签: .net windows powershell windows-7 powershell-2.0


【解决方案1】:

下面是如何使用 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。

【讨论】:

  • 所以我刚刚测试了这段代码,我得到了以下两个错误: 1. 它仍然没有正确编译。编译错误在对@Simon Mourier 回答的评论中。 2、我四次报错:Add-Type C:\path\to\j2zoomon.0.cs The type or namespace name 'var' could not be found
  • 我还想说我理解这是否对于仅 50 次代表来说太过分了。我是一名网络开发人员,这是我正在为广播电台开发的网络应用程序的一部分,我已经为这个问题苦苦挣扎了大约 3 个月,所以感谢你们所做的所有研究
  • 您使用的是哪个版本的 PowerShell 和操作系统?您可以检查 $PSVersionTable.PSVersion.ToString() 和 gwmi win32_operatingsystem |选择版本
  • 谢谢亚当。我假设您没有本地管理员的严厉政策,但如果您有,请查看更新的答案。
  • @HappyBirthdayDelphi_25 这是使用 PowerShell 的 Add-Type cmdlet 编译的 C# 代码。
【解决方案2】:

这是一个示例 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 中使用它。它将返回一个错误级别,您可以使用标准方式检索该级别,例如: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;
  }

【讨论】:

  • 所以我以前从未使用过本机 C#(仅在运行 Powershell 时使用它的某些部分),但问题是,每当我尝试安装 .NET 框架以运行 C# 时,它把我的电脑吓坏了。意思是,我无法在它们上运行 powershell(它甚至不会出现),当我在安装 .NET 框架后尝试安装 Visual Studio 时,它说:“Bootstrapper 停止工作”
  • 好的,你可以清楚地看出,我对这类东西一无所知,但我终于到了我试图用 csc.exe 编译代码的地方我收到以下错误:(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
  • 嗯。你似乎离 C# 知识有点太远了(没有难过的感觉:-)。我能做的是在 github 上创建一个 C++ 项目,其中包含一个 smilar 代码(在 C++ 中)和一个可以从 powershell 使用的 exe(我猜 powershell 可以很容易地使用一个 exe 返回代码)。这样你也不会有任何 .net 框架依赖问题,但你必须发布一个 exe
  • 另一种解决方案是使用 Add-Type blogs.technet.microsoft.com/heyscriptingguy/2013/06/25/… 将即时编译的 C# 添加到 powershell,但从未尝试过...
  • 一点都不难受,是真的!我一直想学习 C#,但我还没有,很多 C#/.NET 的东西都让我头疼。那真是太棒了!
【解决方案3】:

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

Write-DefaultAudioDeviceValue -StreamValue 看起来像您正在寻找的东西。否则,您可以查看他的来源,了解他如何使用 CoreAudioApi 提取这些值

【讨论】:

  • 我记得大约一个月前在我的研究中看到了这一点,现在我记得为什么我无法使用它。 unblock-file cmdlet 在 powershell v2 中不可用
  • @AdamMcGurk 您可以通过右键单击并选择“属性”在 Windows 资源管理器中取消阻止文件。第一个选项卡的底部应该有一个复选框。但是升级超过 PowerShell 2 也是一个非常好的主意。新版本中有很多更好的工具。
  • 我一直记得事情哈哈...我记得这样做,但我得到了This assembly is built by a runtime newer than the currently loaded runtime... 错误。伙计,我很想升级到 Powershell v2,但由于某种原因,每当我在商店的任何计算机上升级 .NET 框架时,都会导致 Powershell 无法使用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多