实施核心音频 API 事件
Implementing Core Audio API events
正在尝试为 Windows Core Audio API(Win7 64 位 Delphi XE5)实现事件。我的 objective 是跟踪音量混合器中的应用程序,以将不在我的列表中的音频会话静音,并调整我的目标应用程序的音量。我成功地枚举了音频设备和会话,将音频静音并在每个会话的基础上调整音量,但我正在为事件而苦苦挣扎。我需要的是在添加新会话和关闭会话时收到通知,以便我可以再次枚举。我可以使用计时器来枚举会话,但我宁愿避免这种情况。
不起作用的特定事件是 IAudioSessionNotification
和 IMMNotificationClient
。
我的问题如下:
- 我为事件推导 classes 的方法是否过于简单?我
在这里找到了一个涉及更多的例子:
Catch audio sessions events
,但它似乎也不起作用(未亲自测试)
- 虽然
IAudioEndpointVolumeCallback
是"working"我觉得代码
闻起来是因为我在 OnNotify 函数中引用 UI 元素
所以我想要一些 feedback/pointers。这是一个有效的实施吗?
我有两个单元:包含主窗体的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;
在主表单单元中,我为所需的接口派生了 classes:
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;
... 最后,class 函数
{ 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;
我认为代码是 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;
正在尝试为 Windows Core Audio API(Win7 64 位 Delphi XE5)实现事件。我的 objective 是跟踪音量混合器中的应用程序,以将不在我的列表中的音频会话静音,并调整我的目标应用程序的音量。我成功地枚举了音频设备和会话,将音频静音并在每个会话的基础上调整音量,但我正在为事件而苦苦挣扎。我需要的是在添加新会话和关闭会话时收到通知,以便我可以再次枚举。我可以使用计时器来枚举会话,但我宁愿避免这种情况。
不起作用的特定事件是 IAudioSessionNotification
和 IMMNotificationClient
。
我的问题如下:
- 我为事件推导 classes 的方法是否过于简单?我 在这里找到了一个涉及更多的例子: Catch audio sessions events ,但它似乎也不起作用(未亲自测试)
- 虽然
IAudioEndpointVolumeCallback
是"working"我觉得代码 闻起来是因为我在 OnNotify 函数中引用 UI 元素 所以我想要一些 feedback/pointers。这是一个有效的实施吗?
我有两个单元:包含主窗体的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;
在主表单单元中,我为所需的接口派生了 classes:
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;
... 最后,class 函数
{ 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;
我认为代码是 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;