Windows 7/8/8.1/10/11 Set Default Audio Device

Share your advanced PureBasic knowledge/code with the community.
AndyMK
Enthusiast
Enthusiast
Posts: 582
Joined: Wed Jul 12, 2006 4:38 pm
Location: UK

Windows 7/8/8.1/10/11 Set Default Audio Device

Post by AndyMK »

Sets the Default Audio Device. Warning, this uses an Undocumented API method that may change in the future. As it stands, this is the only way to do it for now as Microsoft does not officially supply a method for this.

Code: Select all

; -----------------------------------------------------------------------------
;  Author: AndyMK
;  Date  : 02/02/2025
;  PB 6.20 Beta 3 (x64)
; -----------------------------------------------------------------------------
EnableExplicit

#CLSCTX_ALL = $17
#DEVICE_STATE_ACTIVE = $1

; For roles in SetDefaultEndpoint
#ERole_Console         = 0
#ERole_Multimedia      = 1
#ERole_Communications  = 2

; For IPropertyStore
#STGM_READ = $00000000

; -----------------------------------------------------------------------------
;  Structures for property access
; -----------------------------------------------------------------------------
Structure PROPERTYKEY
  Data1.l
  Data2.w
  Data3.w
  Data4.b[8]
  pid.l
EndStructure

Structure PROPVARIANT_CLIPDATA
  cbSize.l
  ulClipFmt.l
  *pClipData.BYTE
EndStructure

Structure PROPVARIANT_BSTRBLOB
  cbSize.l
  *pData.BYTE
EndStructure

Structure PROPVARIANT_BLOB
  cbSize.l
  *pBlobData.BYTE
EndStructure

Structure PROPVARIANT_VERSIONEDSTREAM
  guidVersion.GUID
  *pStream.IStream
EndStructure

Structure PROPVARIANT_CAC
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAUB
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAUI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAUL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAFLT
  cElems.l
  *pElems.FLOAT
EndStructure

Structure PROPVARIANT_CADBL
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CACY
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CADATE
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CABSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CABSTRBLOB
  cElems.l
  *pElems.PROPVARIANT_BSTRBLOB
EndStructure

Structure PROPVARIANT_CABOOL
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CASCODE
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAPROPVARIANT
  cElems.l
  *pElems.PROPVARIANT
EndStructure

Structure PROPVARIANT_CAH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CAUH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CALPSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CALPWSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CAFILETIME
  cElems.l
  *pElems.FILETIME
EndStructure

Structure PROPVARIANT_CACLIPDATA
  cElems.l
  *pElems.PROPVARIANT_CLIPDATA
EndStructure

Structure PROPVARIANT_CACLSID
  cElems.l
  *pElems.CLSID
EndStructure

Structure PROPVARIANT Align #PB_Structure_AlignC
  vt.w
  wReserved1.w
  wReserved2.w
  wReserved3.w
  StructureUnion
    cVal.b
    bVal.b
    iVal.w
    uiVal.w
    lVal.l
    ulVal.l
    intVal.l
    uintVal.l
    hVal.q
    uhVal.q
    fltVal.f
    dblVal.d
    boolVal.w
    scode.l
    cyVal.q
    date.d
    filetime.FILETIME
    *puuid.CLSID
    *pclipdata.PROPVARIANT_CLIPDATA
    bstrVal.i
    bstrblobVal.PROPVARIANT_BSTRBLOB
    blob.PROPVARIANT_BLOB
    *pszVal
    *pwszVal
    *punkVal.IUnknown
    *pdispVal.IDispatch
    *pStream.IStream
    *pStorage.IStorage
    *pVersionedStream.PROPVARIANT_VERSIONEDSTREAM
    *parray.SAFEARRAY
    cac.PROPVARIANT_CAC
    caub.PROPVARIANT_CAUB
    cai.PROPVARIANT_CAI
    caui.PROPVARIANT_CAUI
    cal.PROPVARIANT_CAL
    caul.PROPVARIANT_CAUL
    cah.PROPVARIANT_CAH
    cauh.PROPVARIANT_CAUH
    caflt.PROPVARIANT_CAFLT
    cadbl.PROPVARIANT_CADBL
    cabool.PROPVARIANT_CABOOL
    cascode.PROPVARIANT_CASCODE
    cacy.PROPVARIANT_CACY
    cadate.PROPVARIANT_CADATE
    cafiletime.PROPVARIANT_CAFILETIME
    cauuid.PROPVARIANT_CACLSID
    caclipdata.PROPVARIANT_CACLIPDATA
    cabstr.PROPVARIANT_CABSTR
    cabstrblob.PROPVARIANT_CABSTRBLOB
    calpstr.PROPVARIANT_CALPSTR
    calpwstr.PROPVARIANT_CALPWSTR
    capropvar.PROPVARIANT_CAPROPVARIANT
    *pcVal.BYTE
    *pbVal.BYTE
    *piVal.WORD
    *puiVal.WORD
    *plVal.LONG
    *pulVal.LONG
    *pintVal.LONG
    *puintVal.LONG
    *pfltVal.FLOAT
    *pdblVal.DOUBLE
    *pboolVal.WORD
    *pdecVal.VARIANT_DECIMAL
    *pscode.LONG
    *pcyVal.QUAD
    *pdate.DOUBLE
    *pbstrVal.INTEGER
    *ppunkVal.INTEGER
    *ppdispVal.INTEGER
    *pparray.INTEGER
    *pvarVal.PROPVARIANT
  EndStructureUnion
EndStructure

; -----------------------------------------------------------------------------
;  COM Interfaces
; -----------------------------------------------------------------------------
Interface IMMDeviceEnumerator Extends IUnknown
  EnumAudioEndpoints(dataFlow.l, dwStateMask.l, *ppDevices)
  GetDefaultAudioEndpoint(dataFlow.l, role.l, *ppEndpoint)
  GetDevice(pwstrId, ppDevice)
  RegisterEndpointNotificationCallback(pClient)
  UnregisterEndpointNotificationCallback(pClient)
EndInterface

Interface IMMDeviceCollection Extends IUnknown
  GetCount(*pCount)
  Item(index.l, *ppDevice)
EndInterface

Interface IMMDevice Extends IUnknown
  Activate(iid.i, dwClsCtx.l, pActivationParams.i, ppInterface.i)
  OpenPropertyStore(stgmAccess.l, ppProperties.i)
  GetId(*ppstrId)
  GetState(*pdwState)
EndInterface

Interface IPropertyStore Extends IUnknown
  GetCount(*cProps.l)
  GetAt(i.l, *pKey.PROPERTYKEY)
  GetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
  SetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
  Commit()
EndInterface

; Our undocumented IPolicyConfig with placeholders
; Make sure SetDefaultEndpoint is at the correct vtable slot for your system
Interface IPolicyConfig Extends IUnknown
  Unused1()
  Unused2()
  Unused3()
  Unused4()
  Unused5()
  Unused6()
  Unused7()
  Unused8()
  Unused9()
  Unused10()
  SetDefaultEndpoint(pwstrId, role.l)
EndInterface

; -----------------------------------------------------------------------------
;  Procedure: GetFriendlyName(device.IMMDevice)
;    - Opens property store + retrieves PKEY_Device_FriendlyName
; -----------------------------------------------------------------------------
Procedure.s GetFriendlyName(device.IMMDevice)
  Protected props.IPropertyStore
  Protected propVariant.PROPVARIANT
  Protected hr.l
  Protected result.s = ""

  hr = device\OpenPropertyStore(#STGM_READ, @props)
  If hr = #S_OK And props

    propVariant\vt = 0
    hr = props\GetValue(?PKEY_Device_FriendlyName, @propVariant)
    If hr = #S_OK
      If propVariant\vt = 31 ; VT_LPWSTR (0x1F)
        Protected *wideName = propVariant\pwszVal
        If *wideName
          result = PeekS(*wideName, -1, #PB_Unicode)
        EndIf
      EndIf
    EndIf

    props\Release()
  EndIf

  ProcedureReturn result
EndProcedure

; -----------------------------------------------------------------------------
;  Procedure: ListRenderDevices
;    - Enumerates active render endpoints
;    - Prints each device ID + friendly name
; -----------------------------------------------------------------------------
Procedure ListRenderDevices()
  Protected hr.l, deviceEnumerator.IMMDeviceEnumerator, deviceCollection.IMMDeviceCollection
  Protected count.l, i.l
  Protected device.IMMDevice
  Protected deviceId
  Protected name.s

  CoInitialize_(#Null)

  hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
  If hr <> #S_OK
    Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
    ProcedureReturn
  EndIf

  ; eRender=0, #DEVICE_STATE_ACTIVE=1 => get active render endpoints
  hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection)
  If hr <> #S_OK
    Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
    ProcedureReturn
  EndIf

  deviceCollection\GetCount(@count)
  Debug "Active Render Devices found: " + Str(count)

  For i=0 To count-1
    deviceCollection\Item(i, @device)
    If device
      ; Retrieve device ID
      device\GetId(@deviceId)
      ; Retrieve friendly name
      name = GetFriendlyName(device)

      Debug "Index=" + Str(i)
      Debug "  ID = " + PeekS(deviceId, -1, #PB_Unicode)
      Debug "  FriendlyName = " + name

      device\Release()
    EndIf
  Next

  deviceCollection\Release()
  deviceEnumerator\Release()

  CoUninitialize_()
EndProcedure

; -----------------------------------------------------------------------------
;  Procedure: SetDefaultRenderDeviceByIndex(index.l, role.l)
;    - Sets the device at 'index' as default for role (eConsole, eMultimedia, etc.)
; -----------------------------------------------------------------------------
Procedure SetDefaultRenderDeviceByIndex(index.l, role.l)
  Protected hr.l
  Protected deviceEnumerator.IMMDeviceEnumerator
  Protected deviceCollection.IMMDeviceCollection
  Protected count.l
  Protected device.IMMDevice
  Protected deviceId
  Protected name.s
  
  Protected policyConfig.IPolicyConfig

  CoInitialize_(#Null)

  hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
  If hr <> #S_OK
    Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection) ; eRender=0
  If hr <> #S_OK
    Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  deviceCollection\GetCount(@count)
  If index < 0 Or index >= count
    Debug "Invalid device index: " + Str(index)
    Goto Cleanup
  EndIf

  hr = deviceCollection\Item(index, @device)
  If hr <> #S_OK Or device=#Null
    Debug "Item(" + Str(index) + ") failed, hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  hr = device\GetId(@deviceId)
  If hr <> #S_OK
    Debug "GetId() failed hr=" + Hex(hr)
    Goto Cleanup
  EndIf
  
  name = GetFriendlyName(device)
  Debug "Device ID to set as default: " + name

  ; Create IPolicyConfig
  hr = CoCreateInstance_(?CLSID_CPolicyConfigClient, #Null, #CLSCTX_ALL, ?IID_IPolicyConfig, @policyConfig)
  If hr <> #S_OK
    Debug "CoCreateInstance(CPolicyConfigClient) failed hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  hr = policyConfig\SetDefaultEndpoint(deviceId, role)
  If hr = #S_OK
    Debug "Successfully set default device for role "+Str(role)
  Else
    Debug "SetDefaultEndpoint failed, hr="+Hex(hr)
  EndIf

  ; Cleanup
  Cleanup:
    If policyConfig
      policyConfig\Release()
    EndIf
    If device
      device\Release()
    EndIf
    If deviceCollection
      deviceCollection\Release()
    EndIf
    If deviceEnumerator
      deviceEnumerator\Release()
    EndIf

    CoUninitialize_()
EndProcedure

; -----------------------------------------------------------------------------
;  DataSection
; -----------------------------------------------------------------------------
DataSection
  ; CLSID_MMDeviceEnumerator {BCDE0395-E52F-467C-8E3D-C4579291692E}
  CLSID_MMDeviceEnumerator:
  Data.l $BCDE0395
  Data.w $E52F,$467C
  Data.b $8E,$3D,$C4,$57,$92,$91,$69,$2E

  IID_IMMDeviceEnumerator:
  Data.l $A95664D2
  Data.w $9614,$4F35
  Data.b $A7,$46,$DE,$8D,$B6,$36,$17,$E6

  ; class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
  CLSID_CPolicyConfigClient:
  Data.l $870AF99C
  Data.w $171D,$4F9E
  Data.b $AF,$0D,$E6,$3D,$F4,$0C,$2B,$C9

  ; interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
  IID_IPolicyConfig:
  Data.l $F8679F50
  Data.w $850A,$41CF
  Data.b $9C,$72,$43,$0F,$29,$02,$90,$C8

  ; PKEY_Device_FriendlyName = {A45C254E-DF1C-4EFD-8020-67D146A850E0}, PID=14
  PKEY_Device_FriendlyName:
  Data.l $A45C254E
  Data.w $DF1C,$4EFD
  Data.b $80,$20,$67,$D1,$46,$A8,$50,$E0
  Data.l 14
EndDataSection

; -----------------------------------------------------------------------------
;  Demo
; -----------------------------------------------------------------------------
Procedure Main()
  Debug "=== Listing Devices with Friendly Names ==="
  ListRenderDevices()
  
  Debug "=== Set device with index 1 as default (Console) ==="
  SetDefaultRenderDeviceByIndex(1, #ERole_Console)
  
  ; You can also do:
  ; SetDefaultRenderDeviceByIndex(1, #ERole_Multimedia)
  ; SetDefaultRenderDeviceByIndex(1, #ERole_Communications)
EndProcedure

Main()
End
Fixed PROPVARIANT Structure.
Last edited by AndyMK on Sun Feb 02, 2025 11:24 am, edited 1 time in total.
Quin
Addict
Addict
Posts: 1135
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by Quin »

Oh this is absolutely amazing, I wanted to write a utility to do this a year or so ago with global hotkeys, but I am nowhere near smart enough to wrap COM in PB like this. You amaze me, sir. Hats off to you! :)
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5499
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by Kwai chang caine »

Can be usefull
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
idle
Always Here
Always Here
Posts: 6022
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by idle »

good work thanks for sharing
User_Russian
Addict
Addict
Posts: 1582
Joined: Wed Nov 12, 2008 5:01 pm
Location: Russia

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by User_Russian »

In x64, if you enable purifier, appears message about stack corruption in procedure GetFriendlyName().
Probably the structure PROPVARIANT is incorrect.
User avatar
ChrisR
Addict
Addict
Posts: 1484
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by ChrisR »

Great work, thanks AndyMK :)

About the stack corruption in GetFriendlyName(),
It seems to be solved using the PROPVARIANT structure as defined by nco2k and changing the line "Protected *wideName = propVariant\pwszVal" in GetFriendlyName() procedure, see PKEY_Device_FriendlyName.
AndyMK
Enthusiast
Enthusiast
Posts: 582
Joined: Wed Jul 12, 2006 4:38 pm
Location: UK

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by AndyMK »

Thanks User_Russian/ChrisR. Updated.
BarryG
Addict
Addict
Posts: 4218
Joined: Thu Apr 18, 2019 8:17 am

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by BarryG »

Could a version of this as a module be made, please? It's conflicting heavily with some other volume code I'm using and I can't work out how to keep each code separate. Clashing interface names, labels, etc. :(
infratec
Always Here
Always Here
Posts: 7662
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by infratec »

Where is the problem :?:

Code: Select all

; -----------------------------------------------------------------------------
;  Author: AndyMK
;  Date  : 02/02/2025
;  PB 6.20 Beta 3 (x64)
; -----------------------------------------------------------------------------

DeclareModule SetDefaultRenderDevice
 
 ; For roles in SetDefaultEndpoint
 #ERole_Console         = 0
 #ERole_Multimedia      = 1
 #ERole_Communications  = 2
  
 Declare ListRenderDevices()
 Declare SetDefaultRenderDeviceByIndex(index.l, role.l)
  
EndDeclareModule


Module SetDefaultRenderDevice
  
  EnableExplicit
  
  #CLSCTX_ALL = $17
  #DEVICE_STATE_ACTIVE = $1
  
  
  
  ; For IPropertyStore
  #STGM_READ = $00000000
  
  ; -----------------------------------------------------------------------------
  ;  Structures for property access
  ; -----------------------------------------------------------------------------
  Structure PROPERTYKEY
    Data1.l
    Data2.w
    Data3.w
    Data4.b[8]
    pid.l
  EndStructure
  
  Structure PROPVARIANT_CLIPDATA
    cbSize.l
    ulClipFmt.l
    *pClipData.BYTE
  EndStructure
  
  Structure PROPVARIANT_BSTRBLOB
    cbSize.l
    *pData.BYTE
  EndStructure
  
  Structure PROPVARIANT_BLOB
    cbSize.l
    *pBlobData.BYTE
  EndStructure
  
  Structure PROPVARIANT_VERSIONEDSTREAM
    guidVersion.GUID
    *pStream.IStream
  EndStructure
  
  Structure PROPVARIANT_CAC
    cElems.l
    *pElems.BYTE
  EndStructure
  
  Structure PROPVARIANT_CAUB
    cElems.l
    *pElems.BYTE
  EndStructure
  
  Structure PROPVARIANT_CAI
    cElems.l
    *pElems.WORD
  EndStructure
  
  Structure PROPVARIANT_CAUI
    cElems.l
    *pElems.WORD
  EndStructure
  
  Structure PROPVARIANT_CAL
    cElems.l
    *pElems.LONG
  EndStructure
  
  Structure PROPVARIANT_CAUL
    cElems.l
    *pElems.LONG
  EndStructure
  
  Structure PROPVARIANT_CAFLT
    cElems.l
    *pElems.FLOAT
  EndStructure
  
  Structure PROPVARIANT_CADBL
    cElems.l
    *pElems.DOUBLE
  EndStructure
  
  Structure PROPVARIANT_CACY
    cElems.l
    *pElems.QUAD
  EndStructure
  
  Structure PROPVARIANT_CADATE
    cElems.l
    *pElems.DOUBLE
  EndStructure
  
  Structure PROPVARIANT_CABSTR
    cElems.l
    *pElems.INTEGER
  EndStructure
  
  Structure PROPVARIANT_CABSTRBLOB
    cElems.l
    *pElems.PROPVARIANT_BSTRBLOB
  EndStructure
  
  Structure PROPVARIANT_CABOOL
    cElems.l
    *pElems.WORD
  EndStructure
  
  Structure PROPVARIANT_CASCODE
    cElems.l
    *pElems.LONG
  EndStructure
  
  Structure PROPVARIANT_CAPROPVARIANT
    cElems.l
    *pElems.PROPVARIANT
  EndStructure
  
  Structure PROPVARIANT_CAH
    cElems.l
    *pElems.QUAD
  EndStructure
  
  Structure PROPVARIANT_CAUH
    cElems.l
    *pElems.QUAD
  EndStructure
  
  Structure PROPVARIANT_CALPSTR
    cElems.l
    *pElems.INTEGER
  EndStructure
  
  Structure PROPVARIANT_CALPWSTR
    cElems.l
    *pElems.INTEGER
  EndStructure
  
  Structure PROPVARIANT_CAFILETIME
    cElems.l
    *pElems.FILETIME
  EndStructure
  
  Structure PROPVARIANT_CACLIPDATA
    cElems.l
    *pElems.PROPVARIANT_CLIPDATA
  EndStructure
  
  Structure PROPVARIANT_CACLSID
    cElems.l
    *pElems.CLSID
  EndStructure
  
  Structure PROPVARIANT Align #PB_Structure_AlignC
    vt.w
    wReserved1.w
    wReserved2.w
    wReserved3.w
    StructureUnion
      cVal.b
      bVal.b
      iVal.w
      uiVal.w
      lVal.l
      ulVal.l
      intVal.l
      uintVal.l
      hVal.q
      uhVal.q
      fltVal.f
      dblVal.d
      boolVal.w
      scode.l
      cyVal.q
      date.d
      filetime.FILETIME
      *puuid.CLSID
      *pclipdata.PROPVARIANT_CLIPDATA
      bstrVal.i
      bstrblobVal.PROPVARIANT_BSTRBLOB
      blob.PROPVARIANT_BLOB
      *pszVal
      *pwszVal
      *punkVal.IUnknown
      *pdispVal.IDispatch
      *pStream.IStream
      *pStorage.IStorage
      *pVersionedStream.PROPVARIANT_VERSIONEDSTREAM
      *parray.SAFEARRAY
      cac.PROPVARIANT_CAC
      caub.PROPVARIANT_CAUB
      cai.PROPVARIANT_CAI
      caui.PROPVARIANT_CAUI
      cal.PROPVARIANT_CAL
      caul.PROPVARIANT_CAUL
      cah.PROPVARIANT_CAH
      cauh.PROPVARIANT_CAUH
      caflt.PROPVARIANT_CAFLT
      cadbl.PROPVARIANT_CADBL
      cabool.PROPVARIANT_CABOOL
      cascode.PROPVARIANT_CASCODE
      cacy.PROPVARIANT_CACY
      cadate.PROPVARIANT_CADATE
      cafiletime.PROPVARIANT_CAFILETIME
      cauuid.PROPVARIANT_CACLSID
      caclipdata.PROPVARIANT_CACLIPDATA
      cabstr.PROPVARIANT_CABSTR
      cabstrblob.PROPVARIANT_CABSTRBLOB
      calpstr.PROPVARIANT_CALPSTR
      calpwstr.PROPVARIANT_CALPWSTR
      capropvar.PROPVARIANT_CAPROPVARIANT
      *pcVal.BYTE
      *pbVal.BYTE
      *piVal.WORD
      *puiVal.WORD
      *plVal.LONG
      *pulVal.LONG
      *pintVal.LONG
      *puintVal.LONG
      *pfltVal.FLOAT
      *pdblVal.DOUBLE
      *pboolVal.WORD
      *pdecVal.VARIANT_DECIMAL
      *pscode.LONG
      *pcyVal.QUAD
      *pdate.DOUBLE
      *pbstrVal.INTEGER
      *ppunkVal.INTEGER
      *ppdispVal.INTEGER
      *pparray.INTEGER
      *pvarVal.PROPVARIANT
    EndStructureUnion
  EndStructure
  
  ; -----------------------------------------------------------------------------
  ;  COM Interfaces
  ; -----------------------------------------------------------------------------
  Interface IMMDeviceEnumerator Extends IUnknown
    EnumAudioEndpoints(dataFlow.l, dwStateMask.l, *ppDevices)
    GetDefaultAudioEndpoint(dataFlow.l, role.l, *ppEndpoint)
    GetDevice(pwstrId, ppDevice)
    RegisterEndpointNotificationCallback(pClient)
    UnregisterEndpointNotificationCallback(pClient)
  EndInterface
  
  Interface IMMDeviceCollection Extends IUnknown
    GetCount(*pCount)
    Item(index.l, *ppDevice)
  EndInterface
  
  Interface IMMDevice Extends IUnknown
    Activate(iid.i, dwClsCtx.l, pActivationParams.i, ppInterface.i)
    OpenPropertyStore(stgmAccess.l, ppProperties.i)
    GetId(*ppstrId)
    GetState(*pdwState)
  EndInterface
  
  Interface IPropertyStore Extends IUnknown
    GetCount(*cProps)
    GetAt(i.l, *pKey.PROPERTYKEY)
    GetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
    SetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
    Commit()
  EndInterface
  
  ; Our undocumented IPolicyConfig with placeholders
  ; Make sure SetDefaultEndpoint is at the correct vtable slot for your system
  Interface IPolicyConfig Extends IUnknown
    Unused1()
    Unused2()
    Unused3()
    Unused4()
    Unused5()
    Unused6()
    Unused7()
    Unused8()
    Unused9()
    Unused10()
    SetDefaultEndpoint(pwstrId, role.l)
  EndInterface
  
  ; -----------------------------------------------------------------------------
  ;  Procedure: GetFriendlyName(device.IMMDevice)
  ;    - Opens property store + retrieves PKEY_Device_FriendlyName
  ; -----------------------------------------------------------------------------
  Procedure.s GetFriendlyName(device.IMMDevice)
    Protected props.IPropertyStore
    Protected propVariant.PROPVARIANT
    Protected hr.l
    Protected result.s = ""
    
    hr = device\OpenPropertyStore(#STGM_READ, @props)
    If hr = #S_OK And props
      
      propVariant\vt = 0
      hr = props\GetValue(?PKEY_Device_FriendlyName, @propVariant)
      If hr = #S_OK
        If propVariant\vt = 31 ; VT_LPWSTR (0x1F)
          Protected *wideName = propVariant\pwszVal
          If *wideName
            result = PeekS(*wideName, -1, #PB_Unicode)
          EndIf
        EndIf
      EndIf
      
      props\Release()
    EndIf
    
    ProcedureReturn result
  EndProcedure
  
  ; -----------------------------------------------------------------------------
  ;  Procedure: ListRenderDevices
  ;    - Enumerates active render endpoints
  ;    - Prints each device ID + friendly name
  ; -----------------------------------------------------------------------------
  Procedure ListRenderDevices()
    Protected hr.l, deviceEnumerator.IMMDeviceEnumerator, deviceCollection.IMMDeviceCollection
    Protected count.l, i.l
    Protected device.IMMDevice
    Protected deviceId
    Protected name.s
    
    CoInitialize_(#Null)
    
    hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
    If hr <> #S_OK
      Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
      ProcedureReturn
    EndIf
    
    ; eRender=0, #DEVICE_STATE_ACTIVE=1 => get active render endpoints
    hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection)
    If hr <> #S_OK
      Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
      ProcedureReturn
    EndIf
    
    deviceCollection\GetCount(@count)
    Debug "Active Render Devices found: " + Str(count)
    
    For i=0 To count-1
      deviceCollection\Item(i, @device)
      If device
        ; Retrieve device ID
        device\GetId(@deviceId)
        ; Retrieve friendly name
        name = GetFriendlyName(device)
        
        Debug "Index=" + Str(i)
        Debug "  ID = " + PeekS(deviceId, -1, #PB_Unicode)
        Debug "  FriendlyName = " + name
        
        device\Release()
      EndIf
    Next
    
    deviceCollection\Release()
    deviceEnumerator\Release()
    
    CoUninitialize_()
  EndProcedure
  
  ; -----------------------------------------------------------------------------
  ;  Procedure: SetDefaultRenderDeviceByIndex(index.l, role.l)
  ;    - Sets the device at 'index' as default for role (eConsole, eMultimedia, etc.)
  ; -----------------------------------------------------------------------------
  Procedure SetDefaultRenderDeviceByIndex(index.l, role.l)
    Protected hr.l
    Protected deviceEnumerator.IMMDeviceEnumerator
    Protected deviceCollection.IMMDeviceCollection
    Protected count.l
    Protected device.IMMDevice
    Protected deviceId
    Protected name.s
    
    Protected policyConfig.IPolicyConfig
    
    CoInitialize_(#Null)
    
    hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
    If hr <> #S_OK
      Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
      Goto Cleanup
    EndIf
    
    hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection) ; eRender=0
    If hr <> #S_OK
      Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
      Goto Cleanup
    EndIf
    
    deviceCollection\GetCount(@count)
    If index < 0 Or index >= count
      Debug "Invalid device index: " + Str(index)
      Goto Cleanup
    EndIf
    
    hr = deviceCollection\Item(index, @device)
    If hr <> #S_OK Or device=#Null
      Debug "Item(" + Str(index) + ") failed, hr=" + Hex(hr)
      Goto Cleanup
    EndIf
    
    hr = device\GetId(@deviceId)
    If hr <> #S_OK
      Debug "GetId() failed hr=" + Hex(hr)
      Goto Cleanup
    EndIf
    
    name = GetFriendlyName(device)
    Debug "Device ID to set as default: " + name
    
    ; Create IPolicyConfig
    hr = CoCreateInstance_(?CLSID_CPolicyConfigClient, #Null, #CLSCTX_ALL, ?IID_IPolicyConfig, @policyConfig)
    If hr <> #S_OK
      Debug "CoCreateInstance(CPolicyConfigClient) failed hr=" + Hex(hr)
      Goto Cleanup
    EndIf
    
    hr = policyConfig\SetDefaultEndpoint(deviceId, role)
    If hr = #S_OK
      Debug "Successfully set default device for role "+Str(role)
    Else
      Debug "SetDefaultEndpoint failed, hr="+Hex(hr)
    EndIf
    
    ; Cleanup
    Cleanup:
    If policyConfig
      policyConfig\Release()
    EndIf
    If device
      device\Release()
    EndIf
    If deviceCollection
      deviceCollection\Release()
    EndIf
    If deviceEnumerator
      deviceEnumerator\Release()
    EndIf
    
    CoUninitialize_()
  EndProcedure
  
  ; -----------------------------------------------------------------------------
  ;  DataSection
  ; -----------------------------------------------------------------------------
  DataSection
    ; CLSID_MMDeviceEnumerator {BCDE0395-E52F-467C-8E3D-C4579291692E}
    CLSID_MMDeviceEnumerator:
    Data.l $BCDE0395
    Data.w $E52F,$467C
    Data.b $8E,$3D,$C4,$57,$92,$91,$69,$2E
    
    IID_IMMDeviceEnumerator:
    Data.l $A95664D2
    Data.w $9614,$4F35
    Data.b $A7,$46,$DE,$8D,$B6,$36,$17,$E6
    
    ; class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
    CLSID_CPolicyConfigClient:
    Data.l $870AF99C
    Data.w $171D,$4F9E
    Data.b $AF,$0D,$E6,$3D,$F4,$0C,$2B,$C9
    
    ; interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
    IID_IPolicyConfig:
    Data.l $F8679F50
    Data.w $850A,$41CF
    Data.b $9C,$72,$43,$0F,$29,$02,$90,$C8
    
    ; PKEY_Device_FriendlyName = {A45C254E-DF1C-4EFD-8020-67D146A850E0}, PID=14
    PKEY_Device_FriendlyName:
    Data.l $A45C254E
    Data.w $DF1C,$4EFD
    Data.b $80,$20,$67,$D1,$46,$A8,$50,$E0
    Data.l 14
  EndDataSection
  
EndModule

; -----------------------------------------------------------------------------
;  Demo
; -----------------------------------------------------------------------------


Procedure Main()
  Debug "=== Listing Devices with Friendly Names ==="
  SetDefaultRenderDevice::ListRenderDevices()
  
  Debug "=== Set device with index 0 as default (Console) ==="
  SetDefaultRenderDevice::SetDefaultRenderDeviceByIndex(0, SetDefaultRenderDevice::#ERole_Console)
  
  ; You can also do:
  ; SetDefaultRenderDevice::SetDefaultRenderDeviceByIndex(0, SetDefaultRenderDevice::#ERole_Multimedia)
  ; SetDefaultRenderDevice::SetDefaultRenderDeviceByIndex(0, SetDefaultRenderDevice::#ERole_Communications)
EndProcedure

Main()
Maybe you want to change the Procedure names.
BarryG
Addict
Addict
Posts: 4218
Joined: Thu Apr 18, 2019 8:17 am

Re: Windows 7/8/8.1/10/11 Set Default Audio Device

Post by BarryG »

infratec wrote: Tue Oct 21, 2025 7:14 pmWhere is the problem :?:
I have no experience with Modules and everything I tried wasn't working. But, thanks for your help, as always. :)
Post Reply