实施Core Audio API事件

时间:2016-06-15 04:58:01

标签: delphi audio com delphi-xe5

尝试为Windows Core Audio API(Win7 64位Delphi XE5)实现事件。我的目标是跟踪音量混合器中的应用程序,以静音不在我的列表中的音频会话,并调整目标应用程序的音量。我成功地枚举了音频设备和会话,静音音频并按会话调整音量,但我正在努力解决事件。我需要的是在添加新会话和关闭会话时收到通知,以便我可以再次枚举。我可以使用计时器来枚举会话,但我宁愿避免这种情况。

无效的具体事件包括IAudioSessionNotificationIMMNotificationClient

我的问题如下:

  1. 我为方法派生类的方法是否太简单了?一世 找到了一个更复杂的例子: Catch audio sessions events ,但它似乎也不起作用(未亲自测试)
  2. 虽然IAudioEndpointVolumeCallback是"正在工作"我认为代码 闻起来因为我在OnNotify函数中引用了UI元素 所以我想要一些反馈/指针。这是一个有效的实施吗?
  3. 我有两个单元:uAudioUI包含主窗体,MMDevApi包含Core Audio接口。

    我的代码当前的相关部分看起来像这样(它是一个测试应用程序):

    MMDevApi.pas
    
    ...
      IAudioEndpointVolumeCallback = interface(IUnknown)
      ['{657804FA-D6AD-4496-8A60-352752AF4F89}']
        function OnNotify(pNotify:PAUDIO_VOLUME_NOTIFICATION_DATA):HRESULT; stdcall;
      end;
    
      PIMMNotificationClient = ^IMMNotificationClient;
      IMMNotificationClient = interface(IUnknown)
      ['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
         function OnDefaultDeviceChanged(const flow: EDataFlow; const role: ERole; const pwstrDefaultDevice: LPCWSTR):HRESULT; stdcall;
         function OnDeviceAdded(const pwstrDeviceId: LPCWSTR):HRESULT; stdcall;
         function OnDeviceRemoved(const pwstrDeviceId: LPCWSTR):HRESULT; stdcall;
         function OnDeviceStateChanged(const pwstrDeviceID:LPCWSTR; const dwNewState: DWORD):HRESULT; stdcall;
         function OnPropertyValueChanged(const pwstrDeviceID:LPCWSTR; const key: PROPERTYKEY):HRESULT; stdcall;
      end;
    
      IAudioSessionNotification = interface(IUnknown)
        ['{641DD20B-4D41-49CC-ABA3-174B9477BB08}']
          function OnSessionCreated(const NewSession: IAudioSessionControl): HResult; stdcall;
      end;
    

    在主窗体单元中,我派生了所需接口的类:

    uAudioUI.pas
    ...
    type
    
      TEndpointVolumeCallback = class(TInterfacedObject, IAudioEndpointVolumeCallback)
      public
        function OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT; stdcall;
      end;
    
      TMMNotificationClient = class(TInterfacedObject, IMMNotificationClient)
        function OnDefaultDeviceChanged(const flow: EDataFlow; const role: ERole; const pwstrDefaultDevice: LPCWSTR):HRESULT; stdcall;
        function OnDeviceAdded(const pwstrDeviceId: LPCWSTR):HRESULT; stdcall;
        function OnDeviceRemoved(const pwstrDeviceId: LPCWSTR):HRESULT; stdcall;
        function OnDeviceStateChanged(const pwstrDeviceID:LPCWSTR; const dwNewState: DWORD):HRESULT; stdcall;
        function OnPropertyValueChanged(const pwstrDeviceID:LPCWSTR; const key: PROPERTYKEY):HRESULT; stdcall;
      end;
    
      TAudioMixerSessionCallback = class(TInterfacedObject, IAudioSessionEvents)
        function OnDisplayNameChanged(NewDisplayName:LPCWSTR; EventContext:pGuid):HResult; stdcall;
        function OnIconPathChanged(NewIconPath:LPCWSTR; EventContext:pGuid):HResult; stdcall;
        function OnSimpleVolumeChanged(NewVolume:Single; NewMute:LongBool; EventContext:pGuid):HResult; stdcall;
        function OnChannelVolumeChanged(ChannelCount:uint; NewChannelArray:PSingle; ChangedChannel:uint;
                                    EventContext:pGuid):HResult; stdcall;
        function OnGroupingParamChanged(NewGroupingParam, EventContext:pGuid):HResult; stdcall;
        function OnStateChanged(NewState:uint):HResult; stdcall;  // AudioSessionState
        function OnSessionDisconnected(DisconnectReason:uint):HResult; stdcall; // AudioSessionDisconnectReason
      end;
    
      TAudioSessionCallback = class(TInterfacedObject, IAudioSessionNotification)
        function OnSessionCreated(const NewSession: IAudioSessionControl): HResult; stdcall;
      end;
    

    为简单起见,我使用全局

      private
        { Private declarations }  
        FDefaultDevice           : IMMDevice;
        FAudioEndpointVolume     : IAudioEndpointVolume;
        FDeviceEnumerator        : IMMDeviceEnumerator;
        FAudioClient             : IAudioClient;
        FAudioSessionManager     : IAudioSessionManager2;
        FAudioSessionControl     : IAudioSessionControl2;
        FEndpointVolumeCallback  : IAudioEndpointVolumeCallback;
        FAudioSessionEvents      : IAudioSessionEvents;
        FMMNotificationCallback  : IMMNotificationClient;
        FPMMNotificationCallback : PIMMNotificationClient;
        FAudioSessionCallback    : TAudioSessionCallback;
    

    ...

    procedure TForm1.FormCreate(Sender: TObject);
    var
      ...
    begin
      hr := CoCreateInstance(CLASS_IMMDeviceEnumerator, nil, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, FDeviceEnumerator);
      if hr = ERROR_SUCCESS then
      begin
        hr := FDeviceEnumerator.GetDefaultAudioEndpoint(eRender, eConsole, FDefaultDevice);
        if hr <> ERROR_SUCCESS then Exit;
    
        //get the master audio endpoint
        hr := FDefaultDevice.Activate(IID_IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, IUnknown(FAudioEndpointVolume));
        if hr <> ERROR_SUCCESS then Exit;
        hr := FDefaultDevice.Activate(IID_IAudioClient, CLSCTX_ALL, nil, IUnknown(FAudioClient));
        if hr <> ERROR_SUCCESS then Exit;
    
        //volume handler
        FEndpointVolumeCallback := TEndpointVolumeCallback.Create;
        if FAudioEndpointVolume.RegisterControlChangeNotify(FEndPointVolumeCallback) = ERROR_SUCCESS then
          FEndpointVolumeCallback._AddRef;
    
        //device change / ex: cable unplug handler
        FMMNotificationCallback := TMMNotificationClient.Create;
        FPMMNotificationCallback := @FMMNotificationCallback;
        if FDeviceEnumerator.RegisterEndpointNotificationCallback(FPCableUnpluggedCallback) = ERROR_SUCCESS then
          FMMNotificationCallback._AddRef;
    

    ...然后最后,类函数

    { TEndpointVolumeCallback }
    function TEndpointVolumeCallback.OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
    var
      audioLevel : integer;
    begin
      //NOTE: this works..
      audioLevel := Round(pNotify.fMasterVolume * 100);
      Form1.trackVolumeLevel.Position := audioLevel;
    
      if pNotify.bMuted then
      begin
        form1.trackVolumeLevel.Enabled := False; 
        form1.spdMute.Caption := 'X';
      end
      else
      begin
        form1.trackVolumeLevel.Enabled := True; 
        form1.spdMute.Caption := 'O';
      end;
    
      Result := S_OK;
    
    end;
    
    { TMMNotificaionClient }
    function TMMNotificationClient.OnDefaultDeviceChanged(const flow: EDataFlow; const role: ERole; const pwstrDefaultDevice: LPCWSTR): HRESULT;
    begin
      //NOTE: this crashes - referencing a pointer to add 000000000
      Form1.Label2.Caption := 'Audio device changed';
      Result := S_OK;
    end;
    
    { AudioMixerSessionCallback }
    
    function TAudioMixerSessionCallback.OnSimpleVolumeChanged(NewVolume: Single; NewMute: LongBool; EventContext: PGUID): HRESULT;
    begin
      //NOTE: This works...
      Form1.trackSessionVolumeLevel.Position := Round(NewVolume * 100);
      Form1.Label2.Caption := EventContext.ToString;
      Result := S_OK;
    end;
    
    { AudioSessionCallback }
    
    function TAudioSessionCallback.OnSessionCreated(const NewSession: IAudioSessionControl): HRESULT;
    begin
      //NOTE: This never gets called...
      Form1.Label2.Caption := 'New audio session created';
      Result := S_OK;
    
    end;
    

1 个答案:

答案 0 :(得分:1)

我认为代码是C / C ++的翻译? 使用TInterfacedObject时,您不需要_AddRef等方法,因为TInterfacedObject将处理这些。

另一个建议:我错过了线程实现。通常,这是在构造函数或初始化部分中声明的。

示例:

initialization
  CoInitializeEx(Nil,
                 COINIT_APARTMENTTHREADED);

//Create method
  inherited Create(); 
  CoInitializeEx(Nil,
                 COINIT_APARTMENTTHREADED);

这在使用UI实现时很重要。否则您将不会收到任何活动。 非UI实现(如驱动程序)应使用COINIT_MULTITHREADED模型。

一些注意事项:

不使用PGUID之类的指针,而是使用TGUID。当一个字段在C ++中声明时,它可以从ie pSingle开始。在Delphi中,这应该是单一的。当C ++使用指针指针(如ppSingle)时,在大多数情况下 - 在Delphi中,这将是一个PSingle。

你也宣布function OnChannelVolumeChanged错了。

应该是:

function OnChannelVolumeChanged(ChannelCount: UINT;
                                NewChannelArray: Array of Single;
                                ChangedChannel: UINT;
                                EventContext: TGUID): HResult; stdcall;