Last active
May 11, 2024 14:20
-
-
Save wbokkers/74e05ccc1ee2371ec55c4a7daf551a26 to your computer and use it in GitHub Desktop.
Control the system volume from UWP, using the IAudioEndpointVolume interface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
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
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
Please do not just copy+paste this code. It needs better error handling (and not swallowing all exceptions).