Delphi - 声音变化通知(静音/取消静音)

Delphi - Sound change notification (mute / unmute)

如何获得系统音频更改的通知?

或者回调函数的使用方法

function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): Hresult; stdcall;

首先声明:我不是音频 API 方面的专家。不过,我可以使用 documentation.

让它工作

首先,我们需要掌握一个IMMDeviceEnumerator interface using CoCreateInstance. Then we use the IMMDeviceEnumerator.GetDefaultAudioEndpoint method to obtain the default audio output device. Using the device's Activate method, we request an IAudioEndpointVolume interface and call its RegisterControlChangeNotify方法来订阅音量通知,包括静音和取消静音。

我们必须为这些通知提供一个收件人,并且该收件人必须实现 IAudioEndpointVolumeCallback 接口,该接口指定收件人对象实际接收通知的方式。

在单窗体 GUI 应用程序中,如我为此答案编写的演示应用程序,使用主窗体是有意义的。因此,我们必须让表单实现IAudioEndpointVolumeCallback.OnNotify method. This method is called by the audio system when the volume is changed (or (un)muted), and the notification data is passed in a AUDIO_VOLUME_NOTIFICATION_DATA结构。

我不想在这个方法中接触 GUI 或冒引发异常的风险,所以为了安全起见,我只让这个方法 post 向表单发送包含所需数据的消息。

完整代码:

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 := 7777;
  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.

我有一些代码给你,3 个源代码文件:一个带有 class 处理音量控制通知的单元,一个与 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 交互以在默认音频设备上更改音量时请求通知。

请注意,这不是一个组件,而是一个 class,您可以通过一个界面使用这个 class。请参阅上面的演示应用程序。

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         = [=12=]000000;
    eCapture        = [=12=]000001;
    eAll            = [=12=]000002;

    // Role constant
    eConsole        = [=12=]000000;
    eMultimedia     = [=12=]000001;
    eCommunications = [=12=]000002;

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.