如何获得系统音频更改的通知?
或者如何使用回调函数
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): Hresult; stdcall;
答案 0 :(得分:2)
首先声明:我不是音频 API 方面的专家。不过,我可以使用 documentation 让它工作。
首先,我们需要使用 CoCreateInstance
获得一个 IMMDeviceEnumerator
接口。然后我们使用 IMMDeviceEnumerator.GetDefaultAudioEndpoint
方法获取默认音频输出 device。使用设备的 Activate
方法,我们请求 IAudioEndpointVolume
接口并调用其 RegisterControlChangeNotify
方法订阅音量通知,包括静音和取消静音。
我们必须为这些通知提供一个接收者,并且该接收者必须实现 IAudioEndpointVolumeCallback
接口,该接口指定接收者对象实际如何接收通知。
在单表单 GUI 应用程序中,就像我为这个答案编写的演示应用程序一样,使用主表单是有意义的。因此,我们必须让表单实现 IAudioEndpointVolumeCallback.OnNotify
方法。当音量改变(或(取消)静音)时,音频系统会调用此方法,并且通知数据以 AUDIO_VOLUME_NOTIFICATION_DATA
结构传递。
我不想在此方法中触摸 GUI 或冒引发异常的风险,因此为了安全起见,我只让此方法将消息发送到包含所需数据的表单。
完整代码:
unit OSD;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ActiveX,
ComObj, AudioEndpoint, Gauge;
// Gauge: https://specials.rejbrand.se/dev/controls/gauge/
const
WM_VOLNOTIFY = WM_USER + 1;
type
TSndVolFrm = class(TForm, IAudioEndpointVolumeCallback)
ArcGauge: TArcGauge;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FDeviceEnumerator: IMMDeviceEnumerator;
FMMDevice: IMMDevice;
FAudioEndpointVolume: IAudioEndpointVolume;
function OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
stdcall;
procedure WMVolNotify(var Msg: TMessage); message WM_VOLNOTIFY;
public
end;
var
SndVolFrm: TSndVolFrm;
implementation
uses
Math;
{$R *.dfm}
procedure TSndVolFrm.FormCreate(Sender: TObject);
begin
if not Succeeded(CoInitialize(nil)) then
ExitProcess(1);
OleCheck(CoCreateInstance(CLASS_IMMDeviceEnumerator, nil, CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator, FDeviceEnumerator));
OleCheck(FDeviceEnumerator.GetDefaultAudioEndpoint(0, 0, FMMDevice));
OleCheck(FMMDevice.Activate(IID_IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, FAudioEndpointVolume));
OleCheck(FAudioEndpointVolume.RegisterControlChangeNotify(Self));
end;
procedure TSndVolFrm.FormDestroy(Sender: TObject);
begin
CoUninitialize;
end;
function TSndVolFrm.OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
begin
if pNotify = nil then
Exit(E_POINTER);
try
PostMessage(Handle, WM_VOLNOTIFY, WPARAM(pNotify.bMuted <> False), LPARAM(Round(100 * pNotify.fMasterVolume)));
Result := S_OK;
except
Result := E_UNEXPECTED;
end;
end;
procedure TSndVolFrm.WMVolNotify(var Msg: TMessage);
begin
var LMute := Msg.WParam <> 0;
var LVolume := Msg.LParam;
if LMute then
begin
ArcGauge.ShowCaption := False;
ArcGauge.FgBrush.Color := $777777;
end
else
begin
ArcGauge.ShowCaption := True;
ArcGauge.FgBrush.Color := clHighlight;
end;
ArcGauge.Position := LVolume;
end;
end.
接口单元:
unit AudioEndpoint;
interface
uses
Windows,
Messages,
SysUtils,
ActiveX,
ComObj;
const
CLASS_IMMDeviceEnumerator : TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
IID_IMMDeviceEnumerator : TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
IID_IAudioEndpointVolume : TGUID = '{5CDF2C82-841E-4546-9722-0CF74078229A}';
type
PAUDIO_VOLUME_NOTIFICATION_DATA = ^AUDIO_VOLUME_NOTIFICATION_DATA;
AUDIO_VOLUME_NOTIFICATION_DATA = record
guidEventContext: TGUID;
bMuted: BOOL;
fMasterVolume: Single;
nChannels: UINT;
afChannelVolumes: Single;
end;
IAudioEndpointVolumeCallback = interface(IUnknown)
['{657804FA-D6AD-4496-8A60-352752AF4F89}']
function OnNotify(pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT; stdcall;
end;
IAudioEndpointVolume = interface(IUnknown)
['{5CDF2C82-841E-4546-9722-0CF74078229A}']
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function GetChannelCount(out PInteger): HRESULT; stdcall;
function SetMasterVolumeLevel(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function SetMasterVolumeLevelScalar(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMasterVolumeLevel(out fLevelDB: single): HRESULT; stdcall;
function GetMasterVolumeLevelScaler(out fLevelDB: single): HRESULT; stdcall;
function SetChannelVolumeLevel(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function SetChannelVolumeLevelScalar(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function GetChannelVolumeLevel(nChannel: Integer; out fLevelDB: double): HRESULT; stdcall;
function GetChannelVolumeLevelScalar(nChannel: Integer; out fLevel: double): HRESULT; stdcall;
function SetMute(bMute: Boolean; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out bMute: Boolean): HRESULT; stdcall;
function GetVolumeStepInfo(pnStep: Integer; out pnStepCount: Integer): HRESULT; stdcall;
function VolumeStepUp(pguidEventContext: PGUID): HRESULT; stdcall;
function VolumeStepDown(pguidEventContext: PGUID): HRESULT; stdcall;
function QueryHardwareSupport(out pdwHardwareSupportMask): HRESULT; stdcall;
function GetVolumeRange(out pflVolumeMindB: double; out pflVolumeMaxdB: double; out pflVolumeIncrementdB: double): HRESULT; stdcall;
end;
IAudioMeterInformation = interface(IUnknown)
['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
end;
IPropertyStore = interface(IUnknown)
end;
IMMDevice = interface(IUnknown)
['{D666063F-1587-4E43-81F1-B948E807363F}']
function Activate(const refId: TGUID; dwClsCtx: DWORD; pActivationParams: PInteger; out pEndpointVolume: IAudioEndpointVolume): HRESULT; stdCall;
function OpenPropertyStore(stgmAccess: DWORD; out ppProperties: IPropertyStore): HRESULT; stdcall;
function GetId(out ppstrId: PLPWSTR): HRESULT; stdcall;
function GetState(out State: Integer): HRESULT; stdcall;
end;
IMMDeviceCollection = interface(IUnknown)
['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
end;
IMMNotificationClient = interface(IUnknown)
['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
end;
IMMDeviceEnumerator = interface(IUnknown)
['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
function EnumAudioEndpoints(dataFlow: TOleEnum; deviceState: SYSUINT; DevCollection: IMMDeviceCollection): HRESULT; stdcall;
function GetDefaultAudioEndpoint(EDF: SYSUINT; ER: SYSUINT; out Dev :IMMDevice ): HRESULT; stdcall;
function GetDevice(pwstrId: pointer; out Dev: IMMDevice): HRESULT; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
end;
implementation
end.
答案 1 :(得分:1)
我有一些代码给你,3 个源代码文件:一个带有处理音量控制通知的类的单元,一个与 Windows API 接口的单元和一个简单的演示程序。该演示实际上是您需要详细查看的全部内容。其余的可以被认为是晦涩的支持例程:-)
让我们看看演示程序。它是一个简单的 VCL 表格,上面只有一个 TMemo。它注册音量控制通知并在备忘录中显示一条简单的消息(您可能想要一个漂亮的用户界面)。
代码真的很简单:创建一个指向 TVolumeControl 的接口,为 OnVolumeChange 分配一个事件处理程序并调用 Initialize 方法。当事件触发时,调用 GetLevelInfo 来获取信息并显示它。当表单被销毁时,调用 Dispose 方法停止接收通知。
unit SoundChangeNotificationDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages, Winapi.ActiveX,
System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
System.Win.ComObj,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Ovb.VolumeMonitor,
Ovb.MMDevApi;
type
TSoundChangeDemoForm = class(TForm)
Memo1: TMemo;
protected
FVolumeMonitor : IVolumeMonitor;
procedure VolumeMonitorVolumeChange(Sender : TObject);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
SoundChangeDemoForm: TSoundChangeDemoForm;
implementation
{$R *.dfm}
constructor TSoundChangeDemoForm.Create(AOwner: TComponent);
var
HR : HRESULT;
begin
inherited;
FVolumeMonitor := TVolumeMonitor.Create;
FVolumeMonitor.OnVolumeChange := VolumeMonitorVolumeChange;
HR := FVolumeMonitor.Initialize();
if not SUCCEEDED(HR) then
ShowMessage('Volume control initialization failed');
end;
destructor TSoundChangeDemoForm.Destroy;
begin
FVolumeMonitor.Dispose;
inherited Destroy;
end;
procedure TSoundChangeDemoForm.VolumeMonitorVolumeChange(Sender: TObject);
var
Info: TVOLUME_INFO;
begin
FVolumeMonitor.GetLevelInfo(Info);
Memo1.Lines.Add(Format('Volume change: nStep=%d cSteps=%d Mute=%d',
[Info.nStep, Info.cSteps, Ord(Info.bMuted)]));
end;
辛苦的工作是我命名为 Ovb.VolumeMonitor 的单元。此单元与 Windows API 交互以在默认音频设备上更改音量时请求通知。
请注意,这不是组件而是类,您可以通过接口使用此类。请参阅上面的演示应用。
unit Ovb.VolumeMonitor;
interface
uses
Winapi.Windows, Winapi.Messages, Winapi.ActiveX,
System.SysUtils, System.Variants, System.Classes, System.SyncObjs,
System.Win.ComObj,
Ovb.MMDevApi;
const
WM_VOLUMECHANGE = (WM_USER + 12);
WM_ENDPOINTCHANGE = (WM_USER + 13); // Not implemented yet
type
IVolumeMonitor = interface
['{B06EE2E9-E707-4086-829A-D5664978069F}']
function Initialize() : HRESULT;
procedure Dispose;
function GetLevelInfo(var Info: TVOLUME_INFO) : HRESULT;
function GetOnVolumeChange: TNotifyEvent;
procedure SetOnVolumeChange(const Value: TNotifyEvent);
property OnVolumeChange : TNotifyEvent read GetOnVolumeChange
write SetOnVolumeChange;
end;
TVolumeMonitor = class(TInterfacedObject,
IVolumeMonitor,
IMMNotificationClient,
IAudioEndpointVolumeCallback)
private
FRegisteredForEndpointNotifications : BOOL;
FRegisteredForVolumeNotifications : BOOL;
FDeviceEnumerator : IMMDeviceEnumerator;
FAudioEndpoint : IMMDevice;
FAudioEndpointVolume : IAudioEndpointVolume;
FEndPointCritSect : TRTLCriticalSection;
FWindowHandle : HWND;
FOnVolumeChange : TNotifyEvent;
procedure WndProc(var Msg: TMessage);
procedure WMVolumeChange(var Msg: TMessage);
function GetOnVolumeChange: TNotifyEvent;
procedure SetOnVolumeChange(const Value: TNotifyEvent);
function AttachToDefaultEndpoint() : HRESULT;
function OnNotify(pNotify : PAUDIO_VOLUME_NOTIFICATION_DATA) : HRESULT; stdcall;
public
constructor Create; virtual;
destructor Destroy; override;
function Initialize() : HRESULT;
procedure DetachFromEndpoint();
procedure Dispose;
function GetLevelInfo(var Info: TVOLUME_INFO) : HRESULT;
property OnVolumeChange : TNotifyEvent read GetOnVolumeChange
write SetOnVolumeChange;
end;
implementation
{ TVolumeMonitor }
constructor TVolumeMonitor.Create;
begin
inherited Create;
FWindowHandle := AllocateHWnd(WndProc);
FRegisteredForEndpointNotifications := FALSE;
FRegisteredForVolumeNotifications := FALSE;
FEndPointCritSect.Initialize();
end;
destructor TVolumeMonitor.Destroy;
begin
if FWindowHandle <> INVALID_HANDLE_VALUE then begin
DeallocateHWnd(FWindowHandle);
FWindowHandle := INVALID_HANDLE_VALUE;
end;
FEndPointCritSect.Free;
inherited Destroy;
end;
// Initialize this object. Call after constructor.
function TVolumeMonitor.Initialize: HRESULT;
var
hr : HRESULT;
begin
hr := CoCreateInstance(CLASS_IMMDeviceEnumerator,
nil,
CLSCTX_INPROC_SERVER,
IID_IMMDeviceEnumerator,
FDeviceEnumerator);
if SUCCEEDED(hr) then begin
hr := FDeviceEnumerator.RegisterEndpointNotificationCallback(Self);
if SUCCEEDED(hr) then
hr := AttachToDefaultEndpoint();
end;
Result := hr;
end;
function TVolumeMonitor.AttachToDefaultEndpoint: HRESULT;
var
hr : HRESULT;
begin
FEndPointCritSect.Enter();
// Get the default music & movies playback device
hr := FDeviceEnumerator.GetDefaultAudioEndpoint(eRender, eMultimedia, FAudioEndpoint);
if SUCCEEDED(hr) then begin
// Get the volume control for it
hr := FAudioEndpoint.Activate(IAudioEndpointVolume, CLSCTX_INPROC_SERVER, nil, FAudioEndpointVolume);
if SUCCEEDED(hr) then begin
// Register for callbacks
hr := FAudioEndpointVolume.RegisterControlChangeNotify(self);
FRegisteredForVolumeNotifications := SUCCEEDED(hr);
end;
end;
FEndPointCritSect.Leave();
Result := hr;
end;
// Stop monitoring the device and release all associated references
procedure TVolumeMonitor.DetachFromEndpoint();
begin
FEndPointCritSect.Enter();
if FAudioEndpointVolume <> nil then begin
// be sure to unregister...
if FRegisteredForVolumeNotifications then begin
FAudioEndpointVolume.UnregisterControlChangeNotify(Self);
FRegisteredForVolumeNotifications := FALSE;
end;
FAudioEndpointVolume := nil
end;
if FAudioEndpoint <> nil then
FAudioEndpoint := nil;
FEndPointCritSect.Leave();
end;
// Call when the app is done with this object before calling release.
// This detaches from the endpoint and releases all audio service references.
procedure TVolumeMonitor.Dispose;
begin
DetachFromEndpoint();
if FRegisteredForEndpointNotifications then begin
FDeviceEnumerator.UnregisterEndpointNotificationCallback(Self);
FRegisteredForEndpointNotifications := FALSE;
end;
end;
function TVolumeMonitor.GetLevelInfo(var Info: TVOLUME_INFO): HRESULT;
var
hr : HRESULT;
begin
hr := E_FAIL;
FEndPointCritSect.Enter();
if FAudioEndpointVolume <> nil then begin
hr := FAudioEndpointVolume.GetMute(Info.bMuted);
if SUCCEEDED(hr) then
hr := FAudioEndpointVolume.GetVolumeStepInfo(Info.nStep, Info.cSteps);
end;
FEndPointCritSect.Leave();
Result := hr;
end;
function TVolumeMonitor.GetOnVolumeChange: TNotifyEvent;
begin
Result := FOnVolumeChange;
end;
// Callback for Windows API
function TVolumeMonitor.OnNotify(
pNotify: PAUDIO_VOLUME_NOTIFICATION_DATA): HRESULT;
begin
if FWindowHandle <> INVALID_HANDLE_VALUE then
PostMessage(FWindowHandle, WM_VOLUMECHANGE, 0, 0);
Result := S_OK;
end;
procedure TVolumeMonitor.SetOnVolumeChange(const Value: TNotifyEvent);
begin
FOnVolumeChange := Value;
end;
procedure TVolumeMonitor.WMVolumeChange(var Msg: TMessage);
begin
if Assigned(FOnVolumeChange) then
FOnVolumeChange(Self);
end;
procedure TVolumeMonitor.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_VOLUMECHANGE : WMVolumeChange(Msg);
else
Winapi.Windows.DefWindowProc(FWindowHandle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
end;
最后,为了与 Windows API 交互,我们需要对 Windows 使用的结构和接口进行一些声明。
unit Ovb.MMDevApi;
interface
uses
WinApi.Windows,
WinApi.ActiveX;
const
CLASS_IMMDeviceEnumerator : TGUID = '{BCDE0395-E52F-467C-8E3D-C4579291692E}';
IID_IMMDeviceEnumerator : TGUID = '{A95664D2-9614-4F35-A746-DE8DB63617E6}';
IID_IAudioEndpointVolume : TGUID = '{5CDF2C82-841E-4546-9722-0CF74078229A}';
// Data-flow direction
eRender = $00000000;
eCapture = $00000001;
eAll = $00000002;
// Role constant
eConsole = $00000000;
eMultimedia = $00000001;
eCommunications = $00000002;
type
TAUDIO_VOLUME_NOTIFICATION_DATA = record
guidEventContext : TGUID;
Muted : BOOL;
fMasterVolume : Single;
nChannels : UINT;
afChannelVolumes : array [1..1] of Single;
end;
PAUDIO_VOLUME_NOTIFICATION_DATA = ^TAUDIO_VOLUME_NOTIFICATION_DATA;
TVOLUME_INFO = record
nStep : UINT;
cSteps : UINT;
bMuted : BOOL;
end;
PVOLUME_INFO = ^TVOLUME_INFO;
IAudioEndpointVolumeCallback = interface(IUnknown)
['{657804FA-D6AD-4496-8A60-352752AF4F89}']
function OnNotify(pNotify : PAUDIO_VOLUME_NOTIFICATION_DATA) : HRESULT; stdcall;
end;
IAudioEndpointVolume = interface(IUnknown)
['{5CDF2C82-841E-4546-9722-0CF74078229A}']
function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): HRESULT; stdcall;
function GetChannelCount(out PInteger): HRESULT; stdcall;
function SetMasterVolumeLevel(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function SetMasterVolumeLevelScalar(fLevelDB: single; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMasterVolumeLevel(out fLevelDB: single): HRESULT; stdcall;
function GetMasterVolumeLevelScaler(out fLevelDB: single): HRESULT; stdcall;
function SetChannelVolumeLevel(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function SetChannelVolumeLevelScalar(nChannel: Integer; fLevelDB: double; pguidEventContext: PGUID): HRESULT; stdcall;
function GetChannelVolumeLevel(nChannel: Integer; out fLevelDB: double): HRESULT; stdcall;
function GetChannelVolumeLevelScalar(nChannel: Integer; out fLevel: double): HRESULT; stdcall;
function SetMute(bMute: Boolean; pguidEventContext: PGUID): HRESULT; stdcall;
function GetMute(out bMute: BOOL): HRESULT; stdcall;
function GetVolumeStepInfo(out pnStep: UINT; out pnStepCount: UINT): HRESULT; stdcall;
function VolumeStepUp(pguidEventContext: PGUID): HRESULT; stdcall;
function VolumeStepDown(pguidEventContext: PGUID): HRESULT; stdcall;
function QueryHardwareSupport(out pdwHardwareSupportMask): HRESULT; stdcall;
function GetVolumeRange(out pflVolumeMindB: double; out pflVolumeMaxdB: double; out pflVolumeIncrementdB: double): HRESULT; stdcall;
end;
IAudioMeterInformation = interface(IUnknown)
['{C02216F6-8C67-4B5B-9D00-D008E73E0064}']
end;
IPropertyStore = interface(IUnknown)
end;
IMMDevice = interface(IUnknown)
['{D666063F-1587-4E43-81F1-B948E807363F}']
function Activate(const refId: TGUID; dwClsCtx: DWORD; pActivationParams: PInteger; out pEndpointVolume: IAudioEndpointVolume): HRESULT; stdCall;
function OpenPropertyStore(stgmAccess: DWORD; out ppProperties: IPropertyStore): HRESULT; stdcall;
function GetId(out ppstrId: PLPWSTR): HRESULT; stdcall;
function GetState(out State: Integer): HRESULT; stdcall;
end;
IMMDeviceCollection = interface(IUnknown)
['{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}']
end;
IMMNotificationClient = interface(IUnknown)
['{7991EEC9-7E89-4D85-8390-6C703CEC60C0}']
end;
IMMDeviceEnumerator = interface(IUnknown)
['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
function EnumAudioEndpoints(dataFlow: TOleEnum; deviceState: SYSUINT; DevCollection: IMMDeviceCollection): HRESULT; stdcall;
function GetDefaultAudioEndpoint(EDF: SYSUINT; ER: SYSUINT; out Dev :IMMDevice ): HRESULT; stdcall;
function GetDevice(pwstrId: pointer; out Dev: IMMDevice): HRESULT; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
function UnregisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
end;
implementation
end.