1

how can I get notified of system audio changes?

Or how to use the callback functions

function RegisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function UnregisterControlChangeNotify(AudioEndPtVol: IAudioEndpointVolumeCallback): Integer; stdcall;
function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): Hresult; stdcall;
pcurtis
  • 81
  • 5
  • Fortunately, this is rather easy. I did it when I made my [OSD volume indicator](https://www.rejbrand.se/rejbrand/article.asp?ItemIndex=536). Part of what you need can be found [here](https://stackoverflow.com/questions/16668504/how-to-check-if-the-system-master-volume-is-mute-or-unmute), but you'll need to add a few missing parts yourself. – Andreas Rejbrand Mar 14 '21 at 22:49
  • I already know that. The question was "How to use the callbacks" – pcurtis Mar 15 '21 at 04:21
  • 1
    Yes. I'll write an answer about that when I get home from work, unless someone else has already posted an answer by then. – Andreas Rejbrand Mar 15 '21 at 07:20

2 Answers2

3

First a disclaimer: I am not an expert on the audio APIs. Still, I can get it to work using the documentation.

First, we need to get hold of an 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 method to subscribe to volume notifications, including mute and unmute.

We must provide a recipient for these notifications, and that recipient must implement the IAudioEndpointVolumeCallback interface, which specifies how the recipient object actually does receive the notifications.

In a single-form GUI application, like the demo application I wrote for this answer, it makes sense to use the main form. Hence, we must let the form implement the 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 structure.

I don't want to touch the GUI or risk raising exceptions in this method, so just to feel safe I only let this method post a message to the form with the required data.

Full code:

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.

Screen recorder of application in action, responding to volume changes including mute and unmute.

Interface unit:

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.
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • Just trying to convert to Lazarus – pcurtis Mar 15 '21 at 14:30
  • I can't get this to compile OleCheck(FAudioEndpointVolume.RegisterControlChangeNotify(Self)); – pcurtis Mar 16 '21 at 08:09
  • It compiles fine in Delphi. But you are using FreePascal? Notice that this uses `Self`, the hidden parameter of a *method*. The argument of `RegisterControlChangeNotify` must be the object that implements the `IAudioEndpointVolumeCallback` interface. In my example above, the class that implements this interface is the main form (`TSndVolFrm`), and I call this line of code (`RegisterControlChangeNotify`) from within a method of `TSndVolFrm` (its `OnCreate` handler), so therefore `Self` is the form object. Perhaps you try to use this line outside of such a method, where `Self` isn't found/right? – Andreas Rejbrand Mar 16 '21 at 08:18
  • If so, you need `OleCheck(FAudioEndpointVolume.RegisterControlChangeNotify(MyObjectThatImplementsTheCallbackInterface));` instead. – Andreas Rejbrand Mar 16 '21 at 08:20
  • OK. Fixed that. The last problem is PostMessage(Handle, WM_VOLNOTIFY, WPARAM(pNotify.bMuted <> False), LPARAM(Round(100 * pNotify.fMasterVolume))); If I change it to PostMessage(Handle, WM_VOLNOTIFY, 0, 0); It works (but I dont have information on what, and by how much) – pcurtis Mar 16 '21 at 09:18
  • @pcurtis: Correct. I use WPARAM to encode the mute/not muted status and the LPARAM to encode the volume as an integer between 0 and 100. So you need those. If you just pass 0 and 0 you always get "not muted" and volume 0 of 100. What exactly is the problem? Which values do you see in the debugger? Does it help to declare `AUDIO_VOLUME_NOTIFICATION_DATA = record` as `AUDIO_VOLUME_NOTIFICATION_DATA = packed record` instead? – Andreas Rejbrand Mar 16 '21 at 09:36
  • It will be a pointer thing. Never understood them, never will. The errors I get are Compile Project, Target: project1.exe: Exit code 1, Errors: 2, Hints: 1 unit1.pas(62,54) Error: Illegal qualifier unit1.pas(62,54) Hint: may be pointer dereference is missing unit1.pas(62,54) Fatal: Syntax error, ")" expected but "identifier BMUTED" found – pcurtis Mar 16 '21 at 09:48
  • OK Solved. Just needed to dereference the pointer. Thanks for the help. – pcurtis Mar 16 '21 at 09:59
  • @pcurtis: Ah, Delphi does that automatically. Maybe FreePascal doesn't. I'm happy it all works for you now! – Andreas Rejbrand Mar 16 '21 at 10:01
2

I have some code for you, 3 source code files: A unit with a class handling volume control notification, a unit to interface with Windows API and a simple demo program. The demo is actually all you have to look in details. The rest can be considered as obscure support routines :-)

Let's see the demo program. It is a simple VCL form having only a TMemo on it. It register for volume control notification and display a simple message in the memo (You probably want a nice UI instead).

The code is really very simple: create an interface pointing to TVolumeControl, assign an event handler to the OnVolumeChange and call the Initialize method. When the event fires, call GetLevelInfo to get the information and display it. When the form is destroyed, call Dispose method to stop getting notification.

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;

The hard work is done is a unit I named Ovb.VolumeMonitor. This unit interact with Windows API to request notification when the volume is changed on the default audio device.

Note that this is not a component but a class and you use this class thru an interface. See the demo app above.

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;

Finally, tho interact with Windows API, we need a few declarations for structure and interfaces that Windows make use.

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.
fpiette
  • 11,983
  • 1
  • 24
  • 46
  • Hello, I want to change microphone sounds but I get access violation error. if not Succeeded(pEndpointVolume.SetChannelVolumeLevelScalar(1, 0.7, nil )) then RaiseLastOSError; – yhackup Apr 11 '23 at 12:10