2016-06-15 33 views
3

Windows Core Audio API(Win7 64ビットDelphi XE5)のイベントを実装しようとしています。私の目的は、ボリュームミキサーのアプリケーションを追跡して、自分のリストにないオーディオセッションをミュートし、ターゲットアプリケーションの音量を調整することです。オーディオデバイスとセッションを正常に列挙し、オーディオをミュートし、セッションごとにボリュームを調整しますが、私はイベントに苦労しています。私が必要とするのは、新しいセッションが追加されたときやセッションが閉じられたときに、再び列挙できるように通知することです。私はセッションを列挙するためにタイマーを使うことができましたが、私はそれを避けることを好むでしょう。Core Audio APIイベントの実装

動作しない特定のイベントは、IAudioSessionNotificationIMMNotificationClientです。

私の質問がされては、次のとおりです。

  1. が単純すぎるイベント用のクラスを派生する私のアプローチですか? IAudioEndpointVolumeCallbackは私がUI要素を参照していますので、コード が臭いと思う「作業」されているが
  2. (個人的にテストしていません) Catch audio sessions events が、どちらか動作していないよう:私 はここにはるかに複雑である例を見つけましたOnNotify関数 で私はいくつかのフィードバック/ポインタがほしいと思います。それは有効な実装ですか?

メインフォームを含むuAudioUIと、Core Audioインターフェイスを含むMMDevApiユニットの2つのユニットがあります。簡単にするため

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; 

:私は必要なインタフェースのためのクラスを派生メインフォーム部において

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; 

私のコードの現在の関連する部分は、この(そのテストアプリケーション)のように見えます私はグローバル

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()は、偶数がメインスレッドで実行されていないことを示します。私が読んだことから、synchronize()は設計上悪いと思われ、PostMessage/SendMessageは良いでしょう。 – lowrider

+0

Quesion 1に関して、私はこのようなクラスを実装しました。http://stackoverflow.com/questions/858974/iaudiosessionnotification-anyone-have-working-codeそして同じ効果。イベントは呼び出されません。このエクササイズが私に理解させたのは、PostMessageを使ったそのようなクラスの価値です。イベントデータをクラスに格納し、PostMessageを呼び出して、メインスレッドのクラスからデータを取得できます。 – lowrider

答えて

1

私は、コードはC/C++からの翻訳であると思いますか? TInterfacedObjectを使用する場合、TInterfacedObjectがそれらを処理するため、_AddRefなどのメソッドは必要ありません。

別の提案:私はスレッドの実装が欠落しています。通常これはコンストラクタまたは初期化セクションで宣言されます。

例:

initialization 
    CoInitializeEx(Nil, 
       COINIT_APARTMENTTHREADED); 

または

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

UIの実装を使用する場合にこれが重要です。そうしないと、イベントは受信されません。 UI以外の実装(ドライバなど)では、COINIT_MULTITHREADEDモデルを使用する必要があります。

いくつかの注意:

代わりPGUIDのように、ポインタを使用しての、TGUIDを使用しています。フィールドがC++で宣言されている場合、フィールドは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をシングルスレッドモードで自動的に初期化するので、既定の設定を解除し、上記のようにスレッドCoInitializeExを使用して再初期化する必要がありました。私はその後イベントを受け入れ始めました。 – lowrider