使用Windows Core Audio时出现问题 [英] Problems using Windows Core Audio

查看:240
本文介绍了使用Windows Core Audio时出现问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大家好,

我在使用Windows Core Audio API时遇到了一些严重的问题.
我认为这个问题非常具体,对于一个快速的问题可能太长了.请让我知道讨论板是否是一个更好的选择.
我还用C++标记了这个问题,因为它们是本机API.

简而言之,我想要实现的目标:
我有一个媒体播放器.更改其音量应反映在Windows混音器中,反之亦然.我要更改会话音量,而不是主音量.

它正在处理一个烦人的小异常.当我在Windows混音器中拖动滑块时,它开始将音量设置为疯狂.我已经录制了视频并将其上传到YouTube,以便您可以看到我在说什么.您可以在这里找到它-点击 [ MSDN [ Vannatech [ ^ ].

提前谢谢.

因此,代码:

VolumeManager,一个ViewModel会实例化它.

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.

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:



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

推荐答案

好,这是解决方案.也许对任何想用C#实现相同目的的人都是有帮助的.
在我的解决方案中,我将使用IAudioClient而不是IAudioSessionManager.我也不知道它是否也适用于会话管理器-我没有尝试过,但我想是这样.在解决实际问题之前,我设法使其正常工作.

编辑.
如果我可以否决自己的解决方案,那我一定会做的;)
当我找出问题所在时,我更改了一些代码行.事实证明,我在解决方案的不同部分中设置了音量.非常聪明.那就是产生这种循环"的原因. 因此,忘记第一个解决方案.我更新了下面的代码.您所要做的就是监听VolumeChanged事件.当且仅当它被触发时,更新您的用户界面.

首先,我们需要一个WAVEFORMATEX结构.我不知道为什么我最初错过了那部分,它在MSDN中有详细记录.
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.
[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的新ctor如下所示:



We gonna use that structure to create an instance of IAudioClient.
The new ctor of class VolumeManager looks like this:

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中进行一些更改.



So far for the VolumeManager. Then some changes within 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;
}



就这样;)



That''s it ;)


这篇关于使用Windows Core Audio时出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆