2016-06-15 72 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