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: 1132
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: 5494
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: 5888
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: 1525
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: 1466
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.
Post Reply