我有一些代码给你,3 个源代码文件:一个带有类处理音量控制通知的单元,一个与 Windows API 接口的单元和一个简单的演示程序。该演示实际上是您必须详细查看的所有内容。其余的可以被认为是晦涩的支持例程:-)
让我们看看演示程序。这是一个简单的 VCL 表单,上面只有一个 TMemo。它注册音量控制通知并在备忘录中显示一条简单的消息(您可能想要一个漂亮的 UI)。
代码真的很简单:创建一个指向 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.