Windows 7/8/8.1/10/11 Set Default Audio Device
Posted: Wed Jan 29, 2025 4:16 pm
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.
Fixed PROPVARIANT Structure.
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