Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hello everyone,

I'm running into some serious problems using the Windows Core Audio API.
This question is, as I think, quite specific and maybe much too long for a quick question. Kindly let me know whether the discussion boards are a better place for this.
I also tagged this question with C++ because these are native APIs.

Just in short what I'd like to achieve:
I have a media player. Changing its volume shall be reflected in the Windows sound mixer and vice versa. I'm changing the session volume, not the master volume.

It's working with one little, annoying exception. When I'm dragging the slider within Windows' sound mixer it starts setting the volume like crazy. I've recorded a video and uploaded it to YouTube so that you can see what I'm talking about. You may find it here - click[^]. On the left side you see the sound mixer of Windows, on the right my media player.

Before I post the code, some remarks.
According to MSDN[^] I have to activate IAudioClient from an IMMDevice before I finally can call Initialize on the client and start requesting services like ISimpleVolumeControl or IAudioSessionControl by calling GetService. You can see my implementation in the second comment block (that's WASAPI).
However, my implementation is not working. I get an exception saying the reference is not set to an object. That's why I'm using IAudioSessionManager to acquire the controls.

I would be very thankful for any hints to solve this issue ;)
Ah, and before I forget. I'm using the CoreAudio wrappers from Vannatech[^].

Thanks in advance.

So, the code:

Class VolumeManager, a ViewModel will instantiate this.
C#
public sealed class VolumeManager : INotifyPropertyChanged, IDisposable
{
...
    private readonly EventClient _eventClient;
    private readonly IAudioSessionControl _sessionControl;
    private readonly ISimpleAudioVolume _simpleVolume;
...
    public VolumeManager()
    {
        var type = 
            Type.GetTypeFromCLSID( 
                Guid.Parse( ComCLSIDs.MMDeviceEnumeratorCLSID ) );
        IMMDeviceEnumerator deviceEnumerator = 
            (IMMDeviceEnumerator)Activator.CreateInstance( type );

        IMMDevice device;
        Marshal.ThrowExceptionForHR( 
            deviceEnumerator.GetDefaultAudioEndpoint( 
                DataFlow.eRender, 
                ERole.eMultimedia, 
                out device ) );

        // see: http://msdn.microsoft.com/en-us/library/ms886177.aspx
        // CLSCTX_INPROC_SERVER = 0x1
        // CLSCTX_INPROC_HANDLER = 0x2
        // CLSCTX_LOCAL_SERVER = 0x4
        // CLSCTX_REMOTE_SERVER = 0x10
        const uint CLSCTX_ALL = 0x1 | 0x2 | 0x4 | 0x10;

        object obj;
        Marshal.ThrowExceptionForHR( 
            device.Activate( 
                Guid.Parse( ComIIDs.IAudioSessionManagerIID ), 
                CLSCTX_ALL, 
                IntPtr.Zero, 
                out obj ) );
        IAudioSessionManager manager = (IAudioSessionManager)obj;

        // see: http://msdn.microsoft.com/en-        us/library/windows/desktop/dd371455(v=vs.85).aspx

        // Marshal.ThrowExceptionForHR(
        //     device.Activate(
        //         Guid.Parse( ComIIDs.IAudioClientIID ),
        //         CLSCTX_ALL, 
        //         IntPtr.Zero,
        //         out obj ) );
        // IAudioClient client = (IAudioClient)obj;
            
        // int hr = client.Initialize( 
        //     AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED, 
        //     0, 
        //     0,
        //     0,
        //     IntPtr.Zero,
        //     Guid.NewGuid() );
        //
        // says: reference not set to an object ;(
        // Exception ex = Marshal.GetExceptionForHR( hr );

        // client.GetService( 
        //     Guid.Parse( ComIIDs.IAudioSessionControlIID ), out obj );
        // _sessionControl = (IAudioSessionControl)obj;

        // client.GetService( 
        //     Guid.Parse( ComIIDs.ISimpleAudioVolumeIID ), out obj );
        // _simpleVolume = (ISimpleAudioVolume)obj;
            
        Marshal.ThrowExceptionForHR( 
               manager.GetAudioSessionControl( 
                   streamFlags: 0, 
                   sessionControl: out _sessionControl ) );
        Marshal.ThrowExceptionForHR( 
               manager.GetSimpleAudioVolume( 
                   streamFlags: 0, 
                   audioVolume: out _simpleVolume ) );

        // Marshal.ThrowExceptionForHR( 
        //     manager.GetAudioSessionControl( 
        //         Guid.Empty, 
        //         0,
        //         out _sessionControl ) );
        // Marshal.ThrowExceptionForHR( 
        //     manager.GetSimpleAudioVolume( 
        //         Guid.Empty,
        //         0,
        //         out _simpleVolume ) );

        Marshal.ThrowExceptionForHR( 
            Marshal.FinalReleaseComObject( deviceEnumerator ) );
        Marshal.ThrowExceptionForHR( 
            Marshal.FinalReleaseComObject( device ) );
        Marshal.ThrowExceptionForHR( 
            Marshal.FinalReleaseComObject( manager ) );

        _eventClient = new EventClient();
        Marshal.ThrowExceptionForHR( 
            _sessionControl.RegisterAudioSessionNotification( _eventClient ) );

        _eventClient.VolumeChanged += OnVolumeChanged;
    }

...

    // remarks: e.Volume is float [0,1]
    private void OnVolumeChanged( object sender, VolumeEventArgs e )
    {
        IsMuted = e.IsMuted;
        int scalar = Convert.ToInt32( (e.Volume * 100) + 0.5 );
        Volume = scalar;
    }

...
    private int _volume;
    public int Volume
    {
        get
        {
            return _volume;
        }

        set
        {
            if ( _volume == value )
            {
                return;
            }

            int ihr = -1;
            if ( value < 0 )
            {
                ihr = _simpleVolume.SetMasterVolume( 0, Guid.Empty );
            }

            if ( value > 100 )
            {
                ihr = _simpleVolume.SetMasterVolume( 1, Guid.Empty );
            }

            float scalar = Convert.ToSingle( (double)value / 100 );
            int hr = _simpleVolume.SetMasterVolume( scalar, Guid.Empty );

            if ( hr == 0 || ihr == 0)
            {
                _volume = value;
                OnPropertyChanged( "Volume" );
            }
        }
    }

...

    public void Dispose()
    {
        _sessionControl.UnregisterAudioSessionNotification( _eventClient );
        Marshal.FinalReleaseComObject( _simpleVolume );
        Marshal.FinalReleaseComObject( _sessionControl );
    }

...

}


Class EventClient:

C#
internal class EventClient : IAudioSessionEvents
{
    public delegate void VolumeEventHandler( object sender, VolumeEventArgs e );
    public event VolumeEventHandler VolumeChanged;

...

    public int OnSimpleVolumeChanged( float volume, bool isMuted, ref Guid eventContext )
    {
        if ( VolumeChanged != null )
        {
            var args = new VolumeEventArgs( volume, isMuted, eventContext );
            VolumeChanged( this, args );
        }
           
        return 0;
    }
Posted
Updated 18-Feb-12 2:31am
v3

1 solution

OK, here's the solution. Maybe it is helpful for anybody who's trying to achieve the same with C#.
In my solution I'm gonna use the IAudioClient instead of IAudioSessionManager. I don't know whether it will work for the session manager, too - I didn't try but I guess so. I managed to get it working before I fixed the actual issue.

Edit.
If I could vote down my own solution, I would have done it ;)
While I was figuring out what's wrong I changed some lines of code. Turning out that I was setting the volume in different parts of my solution. Very clever. That were the reason for this "loops".
So, forget about the first solution. I updated the code below. All you have to do is listen to the VolumeChanged event. When and only when it's fired, update your UI.

First we need a WAVEFORMATEX structure. I don't know why I initially missed that part, it is well documented in MSDN.
C#
[StructLayout(LayoutKind.Sequential)]
private struct WaveFormatEx
{
    public ushort wFormatTag;
    public ushort nChannels;
    public uint nSamplesPerSec;
    public uint nAvgBytesPerSec;
    public ushort nBlockAlign;
    public ushort wBitsPerSample;
    public ushort cbSize;
};


We gonna use that structure to create an instance of IAudioClient.
The new ctor of class VolumeManager looks like this:
C#
public VolumeManager()
{
    var type = Type.GetTypeFromCLSID( Guid.Parse( ComCLSIDs.MMDeviceEnumeratorCLSID ) );
    IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)Activator.CreateInstance( type );

    IMMDevice device;
    Marshal.ThrowExceptionForHR(
        deviceEnumerator.GetDefaultAudioEndpoint( EDataFlow.eRender, ERole.eMultimedia, out device ) );

    // see: http://msdn.microsoft.com/en-us/library/ms886177.aspx
    // CLSCTX_INPROC_SERVER = 0x1
    // CLSCTX_INPROC_HANDLER = 0x2
    // CLSCTX_LOCAL_SERVER = 0x4
    // CLSCTX_REMOTE_SERVER = 0x10
    const uint CLSCTX_ALL = 0x1 | 0x2 | 0x4 | 0x10;
            
    object obj;
    Marshal.ThrowExceptionForHR(
        device.Activate( Guid.Parse( ComIIDs.IAudioClientIID ), CLSCTX_ALL, IntPtr.Zero, out obj ) );
    IAudioClient client = (IAudioClient)obj;
            
    IntPtr waveFormatPtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof(WaveFormatEx) ) );
    Marshal.ThrowExceptionForHR( client.GetMixFormat( out waveFormatPtr ) );
    WaveFormatEx waveFormat = (WaveFormatEx)Marshal.PtrToStructure( waveFormatPtr, typeof( WaveFormatEx ) );

    int hr = client.Initialize(
        AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED, 0, 10000000, 0, waveFormatPtr );

    const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = 1 << 31 | 0x889 << 16 | 0x19;
    if ( hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
    {
        uint bufferSize;
        client.GetBufferSize( out bufferSize );
        ulong refTime = (ulong)( ( 10000.0 * 1000 / waveFormat.nSamplesPerSec * bufferSize ) + 0.5 );
        Marshal.FinalReleaseComObject( client );
        Marshal.FreeHGlobal( waveFormatPtr );
        Marshal.ThrowExceptionForHR(
            device.Activate( Guid.Parse( ComIIDs.IAudioClientIID ), CLSCTX_ALL, IntPtr.Zero, out obj ) );
        client = (IAudioClient)obj;

        waveFormatPtr = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( WaveFormatEx ) ) );
        Marshal.ThrowExceptionForHR( client.GetMixFormat( out waveFormatPtr ) );
        Marshal.ThrowExceptionForHR(
            client.Initialize( AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED, 0, refTime, 0, waveFormatPtr ) );
    }
    else
    {
        Marshal.ThrowExceptionForHR( hr );
    }

    Marshal.ThrowExceptionForHR( client.GetService( Guid.Parse( ComIIDs.IAudioSessionControlIID ), out obj ) );
        _sessionControl = (IAudioSessionControl)obj;
    Marshal.ThrowExceptionForHR( client.GetService( Guid.Parse( ComIIDs.ISimpleAudioVolumeIID ), out obj ) );
        _simpleVolume = (ISimpleAudioVolume)obj;

    Marshal.ThrowExceptionForHR( Marshal.FinalReleaseComObject( deviceEnumerator ) );
    Marshal.ThrowExceptionForHR( Marshal.FinalReleaseComObject( device ) );
    Marshal.ThrowExceptionForHR( Marshal.FinalReleaseComObject( client ) );

    Marshal.FreeHGlobal( waveFormatPtr );

    _eventClient = new EventClient();
    Marshal.ThrowExceptionForHR( _sessionControl.RegisterAudioSessionNotification( _eventClient ) );

    _eventClient.DisplayNameChanged += OnDisplayNameChanged;
    _eventClient.IconPathChanged += OnIconPathChanged;
    _eventClient.VolumeChanged += OnVolumeChanged;
}


So far for the VolumeManager. Then some changes within EventClient.
C#
public int OnSimpleVolumeChanged( float volume, bool isMuted, ref Guid eventContext )
{
    if ( VolumeChanged != null && eventContext != Guid.Empty )
    {
        var args = new VolumeEventArgs( volume, isMuted, eventContext );
        VolumeChanged( this, args );
    }

    return 0;
}


That's it ;)
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900