2016-06-15 75 views
3

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

不工作的具体事件是IAudioSessionNotificationIMMNotificationClient

我的问题如下:

  1. 是我的方法来派生类的事件过于简单化了?我 发现得多这里涉及一个例子: Catch audio sessions events ,但它似乎没有工作,要么(没有亲自测试)
  2. 虽然IAudioEndpointVolumeCallback是“工作”我觉得代码 味道,因为我引用的UI元素在OnNotify函数 ,所以我想要一些反馈/指针。这是一个有效的实施?

我有两个单元:uAudioUI,它包含主窗体和包含Core Audio接口的MMDevApi单元。

我的代码电流的相关部分看起来像这样(其一个测试应用程序):

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; 

在主表格单元I派生类所需的接口:

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; 
+0

问题2:有GetCurrentThreadId检查()如果在主线程中发生的事件,如果没有,你必须同步()。 – whosrdaddy

+0

@whordaddy,谢谢。 GetCurrentThreadId()显示even在主线程中没有运行。从我读取的同步()看起来很糟糕,PostMessage/SendMessage会更好。 – lowrider

+0

关于问题1,我实现了一个类似于这个http://stackoverflow.com/questions/858974/iaudiosessionnotification-anyone-have-working-code和相同的效果;事件不会被调用。这个练习让我明白,但是这样的课程在PostMessage中是非常有价值的。我可以将事件数据存储在类中,调用PostMessage,然后从我的主线程中的类中检索数据。 – lowrider

回答

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中,这应该是Single。当C++使用指向指针的指针时(比如ppSingle),那么 - 在大多数情况下 - 在Delphi中,这将是一个PSingle。

您还宣布function OnChannelVolumeChanged错误。

它应该是:

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

对不起,我的答复延迟了,但我放了一会儿。这个回应中的提示帮助我找到了答案。完整的答案是,Delphi以单线程模式自动初始化COM,因此我必须uninitalize默认值,然后使用上面描述的线程CoInitializeEx重新初始化。然后我开始接受事件。 – lowrider