Skip to content

Instantly share code, notes, and snippets.

@wbokkers
Last active May 11, 2022 03:02
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wbokkers/74e05ccc1ee2371ec55c4a7daf551a26 to your computer and use it in GitHub Desktop.
Save wbokkers/74e05ccc1ee2371ec55c4a7daf551a26 to your computer and use it in GitHub Desktop.
Control the system volume from UWP, using the IAudioEndpointVolume interface
s/---
// Control the system volume from UWP, using the IAudioEndpointVolume interface
//
// Wim Bokkers
//
// Credits:
// * Reddit user sunius (https://www.reddit.com/user/sunius)
// See this thread: https://www.reddit.com/r/WPDev/comments/4kqzkb/launch_exe_with_parameter_in_uwp/d3jepi7/
// And this code: https://pastebin.com/cPhVCyWj
//
// The code provided by sunius has two major drawbacks:
// - It uses unsafe code
// - It does not work in Release mode (the app crashes)
//
// Marshalling the Guid pointers as out parameters will fix this.
//
// This code is also available from: https://gist.github.com/wbokkers/74e05ccc1ee2371ec55c4a7daf551a26
//---
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Windows.Media.Devices;
namespace AudioUtils
{
internal enum HResult : uint
{
S_OK = 0
}
public static class VolumeControl
{
public static void ChangeVolumeToMinLevel(double level)
{
if (level > 1)
level = 1;
else if (level < 0)
level = 0;
try
{
var masterVol = GetAudioEndpointVolumeInterface();
if (masterVol == null)
return;
// Make sure that the audio is not muted
masterVol.SetMute(false, Guid.Empty);
// Only adapt volume if the current level is below the specified minimum level
var currentAudioValue = masterVol.GetMasterVolumeLevelScalar();
float newAudioValue = Convert.ToSingle(level);
if (currentAudioValue > newAudioValue)
return;
masterVol.SetMasterVolumeLevelScalar(newAudioValue, Guid.Empty);
}
catch { }
}
private static IAudioEndpointVolume GetAudioEndpointVolumeInterface()
{
var speakerId = MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default);
var completionHandler = new ActivateAudioInterfaceCompletionHandler<IAudioEndpointVolume>();
var hr = ActivateAudioInterfaceAsync(
speakerId,
typeof(IAudioEndpointVolume).GetTypeInfo().GUID,
IntPtr.Zero,
completionHandler,
out var activateOperation);
Debug.Assert(hr == (uint)HResult.S_OK);
return completionHandler.WaitForCompletion();
}
[DllImport("Mmdevapi.dll", ExactSpelling = true, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Error)]
private static extern uint ActivateAudioInterfaceAsync(
[In, MarshalAs(UnmanagedType.LPWStr)]string deviceInterfacePath,
[In, MarshalAs(UnmanagedType.LPStruct)]Guid riid,
[In] IntPtr activationParams,
[In] IActivateAudioInterfaceCompletionHandler completionHandler,
out IActivateAudioInterfaceAsyncOperation activationOperation);
internal class ActivateAudioInterfaceCompletionHandler<T> : IActivateAudioInterfaceCompletionHandler
{
private AutoResetEvent _completionEvent;
private T _result;
public ActivateAudioInterfaceCompletionHandler()
{
_completionEvent = new AutoResetEvent(false);
}
public void ActivateCompleted(IActivateAudioInterfaceAsyncOperation operation)
{
operation.GetActivateResult(out var hr, out var activatedInterface);
Debug.Assert(hr == (uint)HResult.S_OK);
_result = (T)activatedInterface;
var setResult = _completionEvent.Set();
Debug.Assert(setResult != false);
}
public T WaitForCompletion()
{
var waitResult = _completionEvent.WaitOne();
Debug.Assert(waitResult != false);
return _result;
}
}
}
[ComImport]
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAudioEndpointVolume
{
void NotImpl1();
void NotImpl2();
[return: MarshalAs(UnmanagedType.U4)]
uint GetChannelCount();
void SetMasterVolumeLevel(
[In] [MarshalAs(UnmanagedType.R4)] float level,
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
void SetMasterVolumeLevelScalar(
[In] [MarshalAs(UnmanagedType.R4)] float level,
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
[return: MarshalAs(UnmanagedType.R4)]
float GetMasterVolumeLevel();
[return: MarshalAs(UnmanagedType.R4)]
float GetMasterVolumeLevelScalar();
void SetChannelVolumeLevel(
[In] [MarshalAs(UnmanagedType.U4)] uint channelNumber,
[In] [MarshalAs(UnmanagedType.R4)] float level,
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
void SetChannelVolumeLevelScalar(
[In] [MarshalAs(UnmanagedType.U4)] uint channelNumber,
[In] [MarshalAs(UnmanagedType.R4)] float level,
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
void GetChannelVolumeLevel(
[In] [MarshalAs(UnmanagedType.U4)] uint channelNumber,
[Out] [MarshalAs(UnmanagedType.R4)] out float level);
[return: MarshalAs(UnmanagedType.R4)]
float GetChannelVolumeLevelScalar([In] [MarshalAs(UnmanagedType.U4)] uint channelNumber);
void SetMute(
[In] [MarshalAs(UnmanagedType.Bool)] bool isMuted,
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
[return: MarshalAs(UnmanagedType.Bool)]
bool GetMute();
void GetVolumeStepInfo(
[Out] [MarshalAs(UnmanagedType.U4)] out uint step,
[Out] [MarshalAs(UnmanagedType.U4)] out uint stepCount);
void VolumeStepUp([In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
void VolumeStepDown([In] [MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
[return: MarshalAs(UnmanagedType.U4)] // bit mask
uint QueryHardwareSupport();
void GetVolumeRange(
[Out] [MarshalAs(UnmanagedType.R4)] out float volumeMin,
[Out] [MarshalAs(UnmanagedType.R4)] out float volumeMax,
[Out] [MarshalAs(UnmanagedType.R4)] out float volumeStep);
}
[ComImport]
[Guid("72A22D78-CDE4-431D-B8CC-843A71199B6D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IActivateAudioInterfaceAsyncOperation
{
void GetActivateResult(
[MarshalAs(UnmanagedType.Error)]out uint activateResult,
[MarshalAs(UnmanagedType.IUnknown)]out object activatedInterface);
}
[ComImport]
[Guid("41D949AB-9862-444A-80F6-C261334DA5EB")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IActivateAudioInterfaceCompletionHandler
{
void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
}
}
@wbokkers
Copy link
Author

wbokkers commented Jan 23, 2018

Please do not just copy+paste this code. It needs better error handling (and not swallowing all exceptions).

@henrikl2000
Copy link

Hi,
I used your code VolumeControl to adjust the master volume level from my UWP app and it works very well. I would though like to set the volume for my app separately without changing the master volume.
I tried to set volume using SetChannelVolumeLevelScalar without success. ChannelCount shows 2 channels found. I tried both channel 0 and 1 with the following code.
` Public Sub SetChannelVolume(ByVal Channel As Integer, ByVal Level As Double)
If Level > 1 Then
Level = 1
ElseIf Level < 0 Then
Level = 0
End If

    Try
        Dim channelVol = GetAudioEndpointVolumeInterface()
        If channelVol Is Nothing Then
            Return
        End If

        ' Make sure that the audio is not muted
        channelVol.SetMute(False, Guid.Empty)

        Dim newAudioValue As Single = Convert.ToSingle(Level)

        channelVol.SetChannelVolumeLevelScalar(Channel, newAudioValue, Guid.Empty)
    Catch
    End Try
End Sub

`
Would you be so kind to point me in the right direction.
Thank you in advance,
Henrik

@KaimingZhu
Copy link

Thanks for your help, your work helps a lot in my UWP apps developing when I am a loss to deal with it with a deadline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment