5

I wanna set the OS volume on a certain level on keyboard click using unity and c# for example I wanna set the Windows volume(Not the unity) to 70: How Can I do that???

void Update()
{   
    if (Input.GetKeyDown(KeyCode.A))
    {
        //Set Windows Volume 70%      
    }
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
Ammar Ismaeel
  • 651
  • 1
  • 11
  • 21

2 Answers2

16

This requires a plugin. Since this question is for Windows, you can use IAudioEndpointVolume to build a C++ plugin then call it from C#. This post has a working C++ example of how to change volume with IAudioEndpointVolume and you can use it as the base source to create the C++ plugin.


I've gone ahead and cleaned that code up then converted into a dll plugin and placed the DLL at Assets/Plugins folder. You can see how to build the C++ plugin here.

The C++ code:

#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();
    }
}

The C# code:

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);
    }
}

The GetSystemVolume function is used to get the current volume and SetSystemVolume is used to set it. This question is for Windows but you can do a similar thing with the API for other platforms.

To set it to 70% when A key is pressed:

void Update()
{
    if (Input.GetKeyDown(KeyCode.A))
    {
        //Set Windows Volume 70%  
        SetSystemVolume(0.70f, VolumeUnit.Scalar);
    }
}
sonnyb
  • 3,194
  • 2
  • 32
  • 42
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 1
    Exactly What I want...Thank you...Its working well :) – Ammar Ismaeel Jun 07 '18 at 07:13
  • @AmmarIsmaeel Good. Glad I was able to help. – Programmer Jun 07 '18 at 07:25
  • Great answer - all worked flawlessly. My DLL didn't compile until i commented out the first include: //#include "stdafx.h" – niltoid Jan 11 '19 at 22:16
  • Thank you! Just a note to be careful about possible DLL dependencies. A colleague of mine made a DLL based on this answer using Visual Studio 2015, but we found that on some users' Windows 10 machines, there was a [DLLNotFoundException even though the DLL was present](https://stackoverflow.com/q/1246486/5170571). This exception was actually due to missing DLL dependencies (specifically, missing Microsoft Visual C++ Redistributable). In our case, including vcruntime140.dll and api-ms-win-crt-runtime-l1-1-0.dll in the Plugins folder along with this DLL fixed the issue. – sonnyb Jan 15 '19 at 22:27
  • Also, for anyone directly using the above code - note that `defaultDevice` can be `null` if [`IMMDeviceEnumerator.GetDefaultAudioEndpoint`](https://learn.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-getdefaultaudioendpoint) fails or if no device is available, which will then cause a Unity crash. Specifically, I ran into this issue on one user's machine where there were no valid sound playback devices due to a driver issue. (See `Control Panel -> Sound -> Playback` and disable all devices to test for this.) – sonnyb Jan 16 '19 at 20:46
  • for others that get the "pinvokestackimbalance" error, try set the DllImport attribute with CallingConvention.Cdecl – antonio Nov 25 '21 at 09:08
  • Probably should be `VolumeUnit::Scalar` in the usage section for C++ – Sebastian Feb 10 '22 at 20:06
  • Would it be possible to subscribe to an event about when windows sound volume becomes 0 (muted and unmuted)? – Brackets Mar 24 '22 at 18:57
-2

You cannot do this from what I am aware of. There would be no beneficial gain to this and have you seen this done in any game/application?

You can change the master volume of the Unity application, using a slider and also manage other sound effects in the same way

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

Example

public class AudioControl: MonoBehaviour {
void SetVolume(Slider slider) {
    AudioListener.volume = slider.Value;
    // probably save this value to a playerpref so it is remembered. 
    }
}
Rob
  • 109
  • 2
  • 10
  • **"wanna set the Windows volume(Not the unity)"** This is not System volume. You are setting Unity's volume. Please read the question again. – Programmer Jun 06 '18 at 14:15
  • 1
    Please read my answer again where I stated "You cannot do this" and asked him to provide an example of where this was done. Is that not an applicable answer? – Rob Jun 06 '18 at 14:39
  • You can. You can do this. Setting system Volume is different than setting the game volume. OP clearly said that. Your answer did not address the issue here – Programmer Jun 06 '18 at 14:41
  • @Programmer "You cannot" is a valid answer. In trying to see how a C# application could adjust the Windows system volume, I found [this](https://stackoverflow.com/a/13139478/1663383), which is basically a wrapper around a C++ dll...and does not compile in Unity. Visual Studio says its fine, but Unity cannot find `System.Windows`. – Draco18s no longer trusts SE Jun 06 '18 at 16:33
  • @Draco18s "You cannot is a valid answer" true statement but you can. The problem is that OP said clearly stated that the volume to change is not Unity volume but this answer did just that. As for the link you provided, note that it's bad to change volume or perform task with key press. That answer may have been up-voted 100 times but it's still bad. You have to do that properly with the proper API. I plan on adding answer since no proper answer yet. – Programmer Jun 06 '18 at 16:39
  • @Programmer I'm looking for other answers, but I haven't yet found one that actually works. [This one](https://stackoverflow.com/a/294525/1663383) compiles and runs (with some minor adjustments, such as marking things public and wrapping it in a class), but does not actually *modify* the volume setting. – Draco18s no longer trusts SE Jun 06 '18 at 16:51
  • [This solution](https://blog.sverrirs.com/2016/02/windows-coreaudio-api-in-c.html) appears to work, based on comments, but crashes with an Invalid Cast Exception. And that encompasses everything in the first page worth of Google results. – Draco18s no longer trusts SE Jun 06 '18 at 17:07
  • @Draco18s Saw your comment but didn't have time to reply. The proper solution is to use the Windows API. Any Windows API that can do that is fine.In this case, `IAudioEndpointVolume` is more appropriate for that. It works both for getting and setting the volume on Windows. – Programmer Jun 06 '18 at 18:35
  • @Programmer I'm glad a solution *does exist* but in trying to search for it, `IAudioEndpointVolume` (and similar) do not appear. – Draco18s no longer trusts SE Jun 06 '18 at 19:45
  • @Draco18s You mean on the internet or Visual Studio? If you mean on the internet then that's because most already end up using key press which are answers posted on every question about this topic. Haven't seen a complete example on SO about this topic that works without using key press to simulate it. – Programmer Jun 06 '18 at 19:57
  • @Programmer On the internet. And I was searching for "C# change [windows|system] volume" – Draco18s no longer trusts SE Jun 06 '18 at 23:52
  • 1
    @Draco18s I search question like this one with C++ instead of C#. I learned that whenever you need to access or control a hardware, there is a 90 percent chances that the official API which is guaranteed to work is only available in C++. Other programming languages will just implement it with that C++ API sometimes but not most of the times. This is why other the solution in C# won't show the official API, and is usually a very long code or a short code that's not a proper way to do that such as using key press. – Programmer Jun 07 '18 at 00:49
  • I've also seen those that uses bunch of pointers while trying to hack into Windows dll to do a simple task that could have been with C++ right away. When you search with C# and don't find any standard API to do that then forget about it and do the search with C++. It will save you some time. – Programmer Jun 07 '18 at 00:52