Catch audio sessions events

2019-07-04 04:20发布

问题:

I try to write some application that will monitor audio sessions (like SndVol does). I activated IAudioSessionManager2, got list of current audio sessions through IAudioSessionEnumerator, registered for the notification using RegisterSessionNotification method in audio session manager. However, my application never receives notification OnSessionCreated(). Of course, I read the similar topics here: Notifications not sent, IAudioSessionNotification anyone have working code

I read carefully all the answers but I couldn't find what I am doing wrong. Here is my code (Delphi):

TDNAudioSessionManager = class(TDNUnknownObject, IAudioSessionNotification)
private
  FManager: IAudioSessionManager2;
  FList: TDNAudioSessionList;
  function IsManagerValid: Boolean;
protected
  procedure ActivateSessionManager(out AManager: IAudioSessionManager2);
  procedure GetCurrentSessions;
  // IAudioSessionNotification
  function OnSessionCreated(ANewSession: IAudioSessionControl): HRESULT; stdcall;
public
  constructor Create;
  destructor Destroy; override;
end;

constructor TDNAudioSessionManager.Create;
begin
  inherited Create;
  ActivateSessionManager(FManager);
  if IsManagerValid then
  begin
    FList := TDNAudioSessionList.Create;
    FManager.RegisterSessionNotification(Self);
    GetCurrentSessions;
   end;
 end;

destructor TDNAudioSessionManager.Destroy;
begin
  if IsManagerValid then
  begin
    FManager.UnregisterSessionNotification(Self);
    FManager := nil;
  end;
  FreeAndNil(FList);
  inherited Destroy;
 end;

function TDNAudioSessionManager.IsManagerValid: Boolean;
begin
  Result := Assigned(FManager);
end;

procedure TDNAudioSessionManager.GetCurrentSessions;
var
  AEnumerator: IAudioSessionEnumerator;
  ASession: IAudioSessionControl;
  ACount: Integer;
  I: Integer;
begin
  if IsManagerValid then
    if FManager.GetSessionEnumerator(AEnumerator) = S_OK then
    try
      AEnumerator.GetCount(ACount);
      for I := 0 to ACount - 1 do
      begin
        AEnumerator.GetSession(I, ASession);
        FList.Add(ASession);
      end;
    finally
      AEnumerator := nil;
    end;
end;

function TDNAudioSessionManager.OnSessionCreated(ANewSession: IAudioSessionControl): HRESULT;
begin
  FList.Add(ANewSession);
  Result := S_OK;
end;

procedure TDNAudioSessionManager.ActivateSessionManager(out AManager: IAudioSessionManager2);
var
  AEnumerator: IMMDeviceEnumerator;
  ADefault: IMMDevice;
  ARes: HRESULT;
begin
  AManager := nil;
  ARes := CoCreateInstance(CLSID_MMDeviceEnumerator, nil, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, AEnumerator);
  if not ARes = S_OK then Exit;
  if AEnumerator.GetDefaultAudioEndpoint(eRender, eConsole,  ADefault) = S_OK then
  try
    ADefault.Activate(IID_IAudioSessionManager2, CLSCTX_INPROC_SERVER, nil, AManager);
  finally
    ADefault := nil;
  end;
end;

initialization
  CoInitializeEx(nil, COINIT_MULTITHREADED);
end.

Note: TDNUnknownObject is class that implements IUnknown methods.

And one more question: what event does audio session send when application is closed? (in SndVol it is removed from list). I tried OnSessionDisconnected, OnStateChanged (with state AudioSessionExpired) but my app never receive them too.

Thanks in advance!

TDNUnknownObject:

TDNUnknownObject = class(TObject, IUnknown)
protected
  // IUnknown
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
  function QueryInterface(const IID: TGUID; out Obj): HRESULT; virtual; stdcall;
end;

function TDNUnknownObject._AddRef: Integer; stdcall;
begin
  Result := -1;
end;

function TDNUnknownObject._Release: Integer; stdcall;
begin
  Result := -1;
end;

function TDNUnknownObject.QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

回答1:

Well I'm scratching my head about this.. The first question comes up: Why not using the TInterfacedObject? Saves a lot of work and is proven to work correctly. This object is derived from IUnknown and aliased to IInterface, and does exactly what you try to do here. Of course you could try to write your own version, but beware: Delphi compilers are going to be more strict and so you will be easily confrontated with pitfalls like double implementations and name clashes. The compiler, linker and debugger can behave strangely because of those.

Ok a short note from MS: Make sure that the application initializes COM with Multithreaded Apartment (MTA) model by calling CoInitializeEx(Nil, COINIT_MULTITHREADED) in a non-UI thread. If MTA is not initialized, the application does not receive session notifications from the session manager. Threads that run the user interface of an application should be initialized apartment threading model.

There you go: Your initialization section is not correct. It should be:

initialization
  CoInitializeEx(Nil,
                 COINIT_APARTMENTTHREADED);