2016-06-15 3 views
3

Попытка реализовать события для Windows Core Audio API (64-разрядная версия Delphi XE5 для Win7). Моя цель - отслеживать приложения в Volume Mixer для отключения аудиозаписей, отсутствующих в моем списке, и регулировки громкости для моих целевых приложений. Я успешно перечисляю аудиоустройства и сеансы, отключает звук и настраиваю громкость на основе сеанса, но я борюсь с событиями. Мне нужно получать уведомления о добавлении новых сеансов и закрытии сеансов, чтобы я мог снова перечислить. Я мог бы использовать таймер для перечисления сеанса, но я бы предпочел избежать этого.Реализация событий Core Audio API

Конкретные события, которые не работают, - IAudioSessionNotification и IMMNotificationClient.

Мои вопросы следующим образом:

  1. Является ли мой подход к получению классов для событий слишком упрощенным? Я нашел пример, который гораздо больше вовлечены здесь: Catch audio sessions events , но это, кажется, не работает, либо (не тестировал лично)
  2. Хотя IAudioEndpointVolumeCallback «работает» Я думаю, что код пахнет, потому что я ссылки элементы пользовательского интерфейса в функции OnNotify , поэтому мне нужны некоторые обратные ссылки/указатели. Это действительная реализация?

У меня есть два устройства: 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; 

В основной форме блок 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() показывает, что четный не работает в основном потоке. Из того, что я читал synchronize(), кажется, плохо по дизайну и что PostMessage/SendMessage будет лучше. – lowrider

+0

Что касается Quesion 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); 

Это важно при использовании реализации пользовательского интерфейса. В противном случае вы не будете получать никаких событий. Вне реализации пользовательского интерфейса (например, драйверы) следует использовать модель 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 в однопоточном режиме, поэтому мне пришлось uniniitalize по умолчанию, а затем повторно инициализировать с помощью потока CoInitializeEx, как описано выше. Затем я начал получать события. – lowrider

Смежные вопросы