SvenMe Ответов: 1

Проблемы с использованием Windows Core Audio


Всем привет,

Я столкнулся с некоторыми серьезными проблемами при использовании Windows Core Audio API.
Этот вопрос, как мне кажется, довольно специфичен и, возможно, слишком длинен для быстрого вопроса. Пожалуйста, дайте мне знать, являются ли дискуссионные форумы лучшим местом для этого.
Я также отметил этот вопрос следующим образом C++ потому что это собственные API.

Короче говоря чего бы я хотел достичь:
У меня есть медиаплеер. Изменение его громкости должно отражаться в звуковом микшере Windows и наоборот. Я изменяю громкость сеанса, а не основной том.

Это работает с одним маленьким, раздражающим исключением. Когда я перетаскиваю ползунок в звуковом микшере Windows, он начинает устанавливать громкость как сумасшедший. Я записал видео и загрузил его на YouTube, чтобы вы могли видеть, о чем я говорю. Вы можете найти его здесь - щелчок[^]. Слева вы видите звуковой микшер Windows, справа-мой медиаплеер.

Прежде чем я опубликую код, несколько замечаний.
Согласно с MSDN[^] Я должен активировать IAudioClient из IMMDevice прежде чем я наконец смогу позвонить Initialize на клиенте и начать запрашивать такие услуги, как ISimpleVolumeControl или IAudioSessionControl по телефону GetService Вы можете увидеть мою реализацию во втором блоке комментариев (это WASAPI).
Однако моя реализация не работает. Я получаю исключение, говорящее, что ссылка не установлена на объект. Вот почему я использую IAudioSessionManager чтобы получить контроль.

Я был бы очень благодарен за любые подсказки, чтобы решить эту проблему ;)
Ах да, пока я не забыл. Я использую обертки CoreAudio от Vannatech[^].

Заранее спасибо.

Итак, код:

Класс VolumeManager, ViewModel создаст этот экземпляр.
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 );
    }

...

}


Класс EventClient:

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

1 Ответов

Рейтинг:
9

SvenMe

Ладно, вот тебе и решение. Возможно, это полезно для всех, кто пытается достичь того же с помощью C#.
В своем решении я собираюсь использовать IAudioClient вместо IAudioSessionManager Я не знаю, сработает ли это и для менеджера сеансов - я не пробовал, но думаю, что да. Мне удалось заставить его работать, прежде чем я исправил фактическую проблему.

Редактировать.
Если бы я мог проголосовать против своего собственного решения, я бы это сделал ;)
Пока я выяснял, что не так, я изменил несколько строк кода. Оказалось, что я устанавливал громкость в разных частях своего решения. Очень умный. Вот и были причины для этой "петли".
Итак, забудьте о первом решении. Я обновил код ниже. Все, что вам нужно сделать, это прослушать событие VolumeChanged. Когда и только когда он выстрелит, обновите свой пользовательский интерфейс.

Во - первых, нам нужен WAVEFORMATEX структура. Я не знаю, почему я изначально пропустил эту часть, она хорошо документирована в MSDN.

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


Мы будем использовать эту структуру для создания экземпляра IAudioClient.
Новый конструктор класса VolumeManager выглядеть так:
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;
}


До сих пор для VolumeManager Затем некоторые изменения внутри EventClient.
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;
}


Вот и все ;)