如何在Windows中获取和设置系统音量

5

我想在按下键盘时使用Unity和C#将操作系统音量设置为特定级别,例如我想将Windows音量(而不是Unity)设置为70:我该怎么做?

void Update()
{   
    if (Input.GetKeyDown(KeyCode.A))
    {
        //Set Windows Volume 70%      
    }
}
2个回答

16
需要插件支持。由于这个问题是针对Windows的,您可以使用IAudioEndpointVolume来构建一个C++插件,然后从C#中调用它。这篇文章有一个使用 IAudioEndpointVolume 改变音量的工作示例,您可以将其用作创建C++插件的基础源代码。

我已经整理了那段代码并将其转换为了一个dll插件,并将DLL放置在Assets / Plugins文件夹中。 您可以在此处查看如何构建C++插件。

C++代码:

#include "stdafx.h"
#include <windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>

#define DLLExport __declspec(dllexport)

extern "C"
{
    enum class VolumeUnit {
        Decibel,
        Scalar
    };

    //Gets volume
    DLLExport float GetSystemVolume(VolumeUnit vUnit) {
        HRESULT hr;

        // -------------------------
        CoInitialize(NULL);
        IMMDeviceEnumerator *deviceEnumerator = NULL;
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
        IMMDevice *defaultDevice = NULL;

        hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
        deviceEnumerator->Release();
        deviceEnumerator = NULL;

        IAudioEndpointVolume *endpointVolume = NULL;
        hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
        defaultDevice->Release();
        defaultDevice = NULL;

        float currentVolume = 0;
        if (vUnit == VolumeUnit::Decibel) {
            //Current volume in dB
            hr = endpointVolume->GetMasterVolumeLevel(&currentVolume);
        }

        else if (vUnit == VolumeUnit::Scalar) {
            //Current volume as a scalar
            hr = endpointVolume->GetMasterVolumeLevelScalar(&currentVolume);
        }
        endpointVolume->Release();
        CoUninitialize();

        return currentVolume;
    }

    //Sets volume
    DLLExport void SetSystemVolume(double newVolume, VolumeUnit vUnit) {
        HRESULT hr;

        // -------------------------
        CoInitialize(NULL);
        IMMDeviceEnumerator *deviceEnumerator = NULL;
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&deviceEnumerator);
        IMMDevice *defaultDevice = NULL;

        hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
        deviceEnumerator->Release();
        deviceEnumerator = NULL;

        IAudioEndpointVolume *endpointVolume = NULL;
        hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
        defaultDevice->Release();
        defaultDevice = NULL;

        if (vUnit == VolumeUnit::Decibel)
            hr = endpointVolume->SetMasterVolumeLevel((float)newVolume, NULL);

        else    if (vUnit == VolumeUnit::Scalar)
            hr = endpointVolume->SetMasterVolumeLevelScalar((float)newVolume, NULL);

        endpointVolume->Release();

        CoUninitialize();
    }
}

C# 代码:

using System.Runtime.InteropServices;
using UnityEngine;

public class VolumeManager : MonoBehaviour
{
    //The Unit to use when getting and setting the volume
    public enum VolumeUnit
    {
        //Perform volume action in decibels</param>
        Decibel,
        //Perform volume action in scalar
        Scalar
    }

    /// <summary>
    /// Gets the current volume
    /// </summary>
    /// <param name="vUnit">The unit to report the current volume in</param>
    [DllImport("ChangeVolumeWindows")]
    public static extern float GetSystemVolume(VolumeUnit vUnit);
    /// <summary>
    /// sets the current volume
    /// </summary>
    /// <param name="newVolume">The new volume to set</param>
    /// <param name="vUnit">The unit to set the current volume in</param>
    [DllImport("ChangeVolumeWindows")]
    public static extern void SetSystemVolume(double newVolume, VolumeUnit vUnit);

    // Use this for initialization
    void Start()
    {
        //Get volume in Decibel 
        float volumeDecibel = GetSystemVolume(VolumeUnit.Decibel);
        Debug.Log("Volume in Decibel: " + volumeDecibel);

        //Get volume in Scalar 
        float volumeScalar = GetSystemVolume(VolumeUnit.Scalar);
        Debug.Log("Volume in Scalar: " + volumeScalar);

        //Set volume in Decibel 
        SetSystemVolume(-16f, VolumeUnit.Decibel);

        //Set volume in Scalar 
        SetSystemVolume(0.70f, VolumeUnit.Scalar);
    }
}

GetSystemVolume函数用于获取当前音量,而SetSystemVolume函数则用于设置音量。这个问题适用于Windows平台,但你也可以使用其他平台的API来完成类似的操作。

要在按下A键时将其设置为70%,请执行以下操作:

void Update()
{
    if (Input.GetKeyDown(KeyCode.A))
    {
        //Set Windows Volume 70%  
        SetSystemVolume(0.70f, VolumeUnit.Scalar);
    }
}

1
正是我想要的...谢谢...它运行得很好 :) - Ammar Ismaeel
非常好的答案 - 一切都完美无缺。在我注释掉第一个包含文件之前,我的DLL没有编译成功://#include "stdafx.h" - niltoid
谢谢!请注意可能的DLL依赖关系。我的一位同事使用Visual Studio 2015基于这个答案制作了一个DLL,但我们发现在一些用户的Windows 10机器上,即使DLL存在,也会出现DLLNotFoundException异常。实际上,这个异常是由于缺少DLL依赖项(具体来说,是缺少Microsoft Visual C++ Redistributable)引起的。在我们的情况下,将vcruntime140.dll和api-ms-win-crt-runtime-l1-1-0.dll放在插件文件夹中,与这个DLL一起解决了这个问题。 - sonnyb
此外,对于直接使用上述代码的任何人,请注意,如果IMMDeviceEnumerator.GetDefaultAudioEndpoint失败或没有可用设备,则defaultDevice可能为null,这将导致Unity崩溃。具体来说,我在一个用户的机器上遇到了这个问题,由于驱动程序问题,没有有效的声音播放设备。(请参见“控制面板->声音->播放”,禁用所有设备以进行测试。) - sonnyb
对于那些遇到“pinvokestackimbalance”错误的人,请尝试使用CallingConvention.Cdecl设置DllImport属性。 - antonio
显示剩余3条评论

-2

据我所知,你无法这样做。这样做没有任何好处,你在任何游戏/应用程序中见过这样做吗?

您可以使用滑块更改Unity应用程序的主音量,并以同样的方式管理其他声音效果。

http://docs.unity3d.com/ScriptReference/AudioListener.html
http://docs.unity3d.com/ScriptReference/AudioListener-volume.html

例子

public class AudioControl: MonoBehaviour {
void SetVolume(Slider slider) {
    AudioListener.volume = slider.Value;
    // probably save this value to a playerpref so it is remembered. 
    }
}

想要设置Windows音量(而不是Unity的音量)。这不是系统音量。您正在设置Unity的音量。请再次阅读问题。 - Programmer
1
请再仔细阅读我的回答,我说过“你不能这样做”,并要求他提供一个实例来证明。这不是一个适用的答案吗? - Rob
@Draco18s说:“你不能”是一个正确的陈述,但实际上你可以。问题在于OP明确表示要更改的音量不是Unity音量,但这个答案却做到了。至于你提供的链接,请注意使用按键更改音量或执行任务是不好的。那个答案可能已经得到了100次赞,但仍然不好。你必须使用适当的API来正确地完成这个任务。由于还没有合适的答案,我计划添加答案。 - Programmer
这个解决方案看起来是可行的,根据评论,但会因为无效的转换异常而崩溃。这包括了谷歌搜索结果的第一页中的所有内容。 - Draco18s no longer trusts SE
1
@Draco18s 我在寻找类似这个问题的 C++ 解决方案,而不是 C#。我了解到,每当您需要访问或控制硬件时,有 90% 的机会官方的 API 只能在 C++ 中使用且可以保证正常工作。其他编程语言有时只是使用 C++ API 进行实现,但大多数情况下不是这样。这就是为什么 C# 中的解决方案不会显示官方的 API,并且通常是一个非常长的代码或一个不正确的方式来完成任务,例如使用键盘按键。 - Programmer
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接