Change audio output Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Change audio output

Post by Flipeador » 15 Mar 2021, 13:34

It works for me (W10 Pro 20H2 19042.867). Try this script with the latest version of AutoHotkey v2.

Code: Select all

#Requires AutoHotkey v2.0-a136-feda41f4
; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
; https://web.archive.org/web/20190317012739/http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/

List := EnumAudioEndpoints()

Devices := ""
for Device in List
    Devices .= Format("{} ({})`n`n", Device["Name"], Device["ID"])
MsgBox(Devices)

F6::
{
    SetDefaultEndpoint(GetDeviceID(List, "DENON-AVR (NVIDIA High Definition Audio)"))
    SetDefaultEndpoint(GetDeviceID(List, "Microphone (Yeti Stereo Microphone)"))
}

F7::
{
    SetDefaultEndpoint(GetDeviceID(List, "Headset Earphone (3- CORSAIR VOID ELITE Wireless Gaming Dongle)"))
    SetDefaultEndpoint(GetDeviceID(List, "Headset Microphone (3- CORSAIR VOID ELITE Wireless Gaming Dongle)"))
}

/*
    Generates a collection of audio endpoint devices that meet the specified criteria.
    Parameters:
        DataFlow:
            The data-flow direction for the endpoint devices in the collection.
            0   Audio rendering stream. Audio data flows from the application to the audio endpoint device, which renders the stream.
            1   Audio capture stream. Audio data flows from the audio endpoint device that captures the stream, to the application.
            2   Audio rendering or capture stream. Audio data can flow either from the application to the audio endpoint device, or from the audio endpoint device to the application.
        StateMask:
            The state or states of the endpoints that are to be included in the collection.
            1   Active. The audio adapter that connects to the endpoint device is present and enabled. In addition, if the endpoint device plugs into a jack on the adapter, then the endpoint device is plugged in.
            2   Disabled. The user has disabled the device in the Windows multimedia control panel (Mmsys.cpl).
            4   Not present. The audio adapter that connects to the endpoint device has been removed or disabled.
            8   Unplugged. The audio adapter that contains the jack for the endpoint device is present and enabled, but the endpoint device is not plugged into the jack. Only a device with jack-presence detection can be in this state.
    Return value:
        Returns an array of Map objects with the following keys:
        ID      Endpoint ID string that identifies the audio endpoint device.
        Name    The friendly name of the endpoint device.
*/
EnumAudioEndpoints(DataFlow := 2, StateMask := 1)
{
    List := []

    ; IMMDeviceEnumerator interface.
    ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nn-mmdeviceapi-immdeviceenumerator
    IMMDeviceEnumerator := ComObject("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")

    ; IMMDeviceEnumerator::EnumAudioEndpoints method.
    ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-enumaudioendpoints
    ComCall(3, IMMDeviceEnumerator, "UInt", DataFlow, "UInt", StateMask, "UPtrP", &IMMDeviceCollection:=0)

    ; IMMDeviceCollection::GetCount method.
    ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdevicecollection-getcount
    ComCall(3, IMMDeviceCollection, "UIntP", &DevCount:=0)  ; Retrieves a count of the devices in the device collection.

    loop DevCount
    {
        List.Push(Device := Map())

        ; IMMDeviceCollection::Item method.
        ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdevicecollection-item
        ComCall(4, IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", &IMMDevice:=0)

        ; IMMDevice::GetId method.
        ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdevice-getid
        ComCall(5, IMMDevice, "PtrP", &pBuffer:=0)
        Device["ID"] := StrGet(pBuffer)
        DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)

        ; MMDevice::OpenPropertyStore method.
        ; https://docs.microsoft.com/en-us/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdevice-openpropertystore
        ComCall(4, IMMDevice, "UInt", 0x00000000, "UPtrP", &IPropertyStore:=0)

        Device["Name"] := GetDeviceProp(IPropertyStore, "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", 14)

        ObjRelease(IPropertyStore)
        ObjRelease(IMMDevice)
    }

    ObjRelease(IMMDeviceCollection)

    return List
}

/*
    Set default audio render endpoint.
    Role:
        0x1   Default Device.
        0x2   Default Communication Device.
*/
SetDefaultEndpoint(DeviceID, Role := 3)
{
    ; Undocumented COM-interface IPolicyConfig.
    IPolicyConfig := ComObject("{870AF99C-171D-4F9E-AF0D-E63Df40c2BC9}", "{F8679F50-850A-41CF-9C72-430F290290C8}")
    if (Role & 0x1)
        ComCall(13, IPolicyConfig, "Str", DeviceID, "Int", 0)  ; Default Device
    if (Role & 0x2)
        ComCall(13, IPolicyConfig, "Str", DeviceID, "Int", 2)  ; Default Communication Device
}

/*
    Device Properties (Core Audio APIs)
    https://docs.microsoft.com/en-us/windows/win32/coreaudio/device-properties

    026E516E-B814-414B-83CD-856D6FEF4822, 2   The friendly name of the audio adapter to which the endpoint device is attached.
    A45C254E-DF1C-4EFD-8020-67D146A850E0, 2   The device description of the endpoint device.
    A45C254E-DF1C-4EFD-8020-67D146A850E0,14   The friendly name of the endpoint device.
*/
InitDeviceProp(clsid, n)
{
    clsid := CLSIDFromString(clsid, Buffer(16+4))
    NumPut("Int", n, clsid, 16)
    return clsid
}

GetDeviceProp(ptr, clsid, n)
{
    ; IPropertyStore::GetValue method.
    ; https://docs.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-ipropertystore-getvalue
    ComCall(5, ptr, "Ptr", InitDeviceProp(clsid, n), "Ptr", pvar := PropVariant())
    return String(pvar)
}

GetDeviceID(list, name)
{
    for device in list
        if InStr(device["Name"], name)
            return device["ID"]
    throw
}

CLSIDFromString(Str, Buffer := 0)
{
    if (!Buffer)
        Buffer := Buffer(16)
    DllCall("Ole32\CLSIDFromString", "Str", Str, "Ptr", Buffer, "HRESULT")
    return Buffer
}

class PropVariant
{
    __New()
    {
        this.buffer := Buffer(A_PtrSize == 4 ? 16 : 24)
        this.ptr    := this.buffer.ptr
        this.size   := this.buffer.size
    }

    __Delete()
    {
        DllCall("Ole32\PropVariantClear", "Ptr", this.ptr, "HRESULT")
    }

    ToString()
    {
        return StrGet(NumGet(this.ptr, 8, "UPtr"))  ; LPWSTR PROPVARIANT.pwszVal
    }
}
Updated 20210606.

0x00
Posts: 89
Joined: 22 Jan 2019, 13:12

Re: Change audio output

Post by 0x00 » 16 Mar 2021, 11:24

Flipeador wrote:
02 Jun 2018, 12:38
Use F1, F2 and F3.

Code: Select all

; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
Devices := {}
IMMDeviceEnumerator := ComObjCreate("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")

; IMMDeviceEnumerator::EnumAudioEndpoints
; eRender = 0, eCapture, eAll
; 0x1 = DEVICE_STATE_ACTIVE
DllCall(NumGet(NumGet(IMMDeviceEnumerator+0)+3*A_PtrSize), "UPtr", IMMDeviceEnumerator, "UInt", 0, "UInt", 0x1, "UPtrP", IMMDeviceCollection, "UInt")
ObjRelease(IMMDeviceEnumerator)

; IMMDeviceCollection::GetCount
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+3*A_PtrSize), "UPtr", IMMDeviceCollection, "UIntP", Count, "UInt")
Loop % (Count)
{
    ; IMMDeviceCollection::Item
    DllCall(NumGet(NumGet(IMMDeviceCollection+0)+4*A_PtrSize), "UPtr", IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", IMMDevice, "UInt")

    ; IMMDevice::GetId
    DllCall(NumGet(NumGet(IMMDevice+0)+5*A_PtrSize), "UPtr", IMMDevice, "UPtrP", pBuffer, "UInt")
    DeviceID := StrGet(pBuffer, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)

    ; IMMDevice::OpenPropertyStore
    ; 0x0 = STGM_READ
    DllCall(NumGet(NumGet(IMMDevice+0)+4*A_PtrSize), "UPtr", IMMDevice, "UInt", 0x0, "UPtrP", IPropertyStore, "UInt")
    ObjRelease(IMMDevice)

    ; IPropertyStore::GetValue
    VarSetCapacity(PROPVARIANT, A_PtrSize == 4 ? 16 : 24)
    VarSetCapacity(PROPERTYKEY, 20)
    DllCall("Ole32.dll\CLSIDFromString", "Str", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "UPtr", &PROPERTYKEY)
    NumPut(14, &PROPERTYKEY + 16, "UInt")
    DllCall(NumGet(NumGet(IPropertyStore+0)+5*A_PtrSize), "UPtr", IPropertyStore, "UPtr", &PROPERTYKEY, "UPtr", &PROPVARIANT, "UInt")
    DeviceName := StrGet(NumGet(&PROPVARIANT + 8), "UTF-16")    ; LPWSTR PROPVARIANT.pwszVal
    DllCall("Ole32.dll\CoTaskMemFree", "UPtr", NumGet(&PROPVARIANT + 8))    ; LPWSTR PROPVARIANT.pwszVal
    ObjRelease(IPropertyStore)

    ObjRawSet(Devices, DeviceName, DeviceID)
}
ObjRelease(IMMDeviceCollection)
Return


F1:: SetDefaultEndpoint( GetDeviceID(Devices, "AMD HDMI") )
F2:: SetDefaultEndpoint( GetDeviceID(Devices, "Headphones") )
F3:: SetDefaultEndpoint( GetDeviceID(Devices, "Speakers") )

SetDefaultEndpoint(DeviceID)
{
    IPolicyConfig := ComObjCreate("{870af99c-171d-4f9e-af0d-e63df40c2bc9}", "{F8679F50-850A-41CF-9C72-430F290290C8}")
    DllCall(NumGet(NumGet(IPolicyConfig+0)+13*A_PtrSize), "UPtr", IPolicyConfig, "UPtr", &DeviceID, "UInt", 0, "UInt")
    ObjRelease(IPolicyConfig)
}

GetDeviceID(Devices, Name)
{
    For DeviceName, DeviceID in Devices
        If (InStr(DeviceName, Name))
            Return DeviceID
}


AutoHotkey v2: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=49980&p=387886#p387886.
This works perfectly, as does the v2 version in last page, but just to better conceptually understand how the version above works, could you possibly modify it so as to change input devices instead of output devices. Can't seem to figure how to differentiate one from the other, appreciate it.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Change audio output

Post by Flipeador » 16 Mar 2021, 12:50

@0x00 Look at the DataFlow parameter in the IMMDeviceEnumerator::EnumAudioEndpoints call. Set it to 1 (eCapture) to list input devices.

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 16 Mar 2021, 12:58

@Flipeador Any chance you've seen my question? Sorry to disturb, I felt you'd be the right person to ask. Thank you.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Change audio output

Post by Flipeador » 16 Mar 2021, 13:07

@IVWidth That was my response. I have also read your email. :)

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 16 Mar 2021, 13:15

Thank you so much! I'm new to the forum, I don't know how I missed your reply but I'm eternally grateful.

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 16 Mar 2021, 14:23

@Flipeador When I run the script I get an error from this section:

Code: Select all

List := EnumAudioEndpoints()

Devices := ""
for Device in List
    Devices .= Format("{} ({})`n`n", Device["Name"], Device["ID"])
MsgBox(Devices)
https://imgur.com/a/Zvvh8yc

Thoughts?

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Change audio output

Post by Flipeador » 16 Mar 2021, 15:06

@IVWidth You must run the script with AHK v2: https://www.autohotkey.com/download/ahk-v2.zip.

0x00
Posts: 89
Joined: 22 Jan 2019, 13:12

Re: Change audio output

Post by 0x00 » 16 Mar 2021, 17:09

Flipeador wrote:
16 Mar 2021, 12:50
@0x00 Look at the DataFlow parameter in the IMMDeviceEnumerator::EnumAudioEndpoints call. Set it to 1 (eCapture) to list input devices.
Much thanks :salute:

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 16 Mar 2021, 17:33

Thank you for that. I thought I had. I've installed the binaries according to the instructions. Maybe I should have loaded 32 bit instead?

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Change audio output

Post by Flipeador » 16 Mar 2021, 18:34

@IVWidth v2 does not come with any installer. Just download the .zip file from the link I showed you, extract AutoHotkeyU64.exe and then drag your .ahk script over the .exe. The 32-bit version should work as well.

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 17 Mar 2021, 13:07

Good news and bad news time!

Good news is I got the script to show me the window with the device details. Very cool.
Bad news is it errored out after that again.
Good news is it turns out that M$ didn't change anything - except the names of my speakers and mic. So your original script still works, I just missed that the names of the devices had changed and was able to fix it.
Bad news is obvious, I put you to a load of unnecessary work.
Good news is I learned a lot.

So thank you for that and for all your expertise and help, then and now. I'm off to enjoy my updated functionality.

BobiderBob
Posts: 1
Joined: 08 May 2021, 15:28

Re: Change audio output

Post by BobiderBob » 08 May 2021, 15:39

Is it possible to change the Spatial Audio to "Dolby Atmos for Headphones" using this script, as well?
I am still pretty new to autohotkey and don't quite understand the magic of these scripts :D

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 09 May 2021, 01:07

BobiderBob wrote:
08 May 2021, 15:39
Is it possible to change the Spatial Audio to "Dolby Atmos for Headphones" using this script, as well?
I am still pretty new to autohotkey and don't quite understand the magic of these scripts :D
It will work with any audio setup, all you have to do is type it exactly how it shows in your available audio options and the script will work. Just remember to check the exactness of your entry (spaces and parenthesis, etc.). Windows is your friend and sometimes changes what things are called to keep you on your toes.

kuzyn007
Posts: 4
Joined: 22 Sep 2017, 07:15

Re: Change audio output

Post by kuzyn007 » 30 May 2021, 18:04

Hi, I tried script in from post above in AHKv2 but Im getting this error:

Anyone knows the reason? :)
Attachments
image.png
image.png (2.36 KiB) Viewed 6145 times

gregster
Posts: 8921
Joined: 30 Sep 2013, 06:48

Re: Change audio output

Post by gregster » 30 May 2021, 18:25

kuzyn007 wrote:
30 May 2021, 18:04
Hi, I tried script in from post above in AHKv2 but Im getting this error:

Anyone knows the reason? :)
Looks like the v1 code, actually.

But v2-alpha might have had some breaking changes in the meantime.

kuzyn007
Posts: 4
Joined: 22 Sep 2017, 07:15

Re: Change audio output

Post by kuzyn007 » 06 Jun 2021, 12:12

gregster wrote:
30 May 2021, 18:25
Looks like the v1 code, actually.

But v2-alpha might have had some breaking changes in the meantime.
Thanks, scripts works with older version of ahk v2

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 08 Jun 2021, 23:26

Okay, new feature desired:

Has anyone isolated the lines or have lines that can be added to this script that ALSO switches from Stereo to 5.1 (for example) and back?

noname6500
Posts: 18
Joined: 29 Oct 2021, 04:07

Re: Change audio output

Post by noname6500 » 27 Nov 2021, 05:33

Flipeador wrote:
02 Jun 2018, 10:17
Tested on WIN_10 ;)

Code: Select all

; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
Devices := {}
IMMDeviceEnumerator := ComObjCreate("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")

; IMMDeviceEnumerator::EnumAudioEndpoints
; eRender = 0, eCapture, eAll
; 0x1 = DEVICE_STATE_ACTIVE
DllCall(NumGet(NumGet(IMMDeviceEnumerator+0)+3*A_PtrSize), "UPtr", IMMDeviceEnumerator, "UInt", 0, "UInt", 0x1, "UPtrP", IMMDeviceCollection, "UInt")
ObjRelease(IMMDeviceEnumerator)

; IMMDeviceCollection::GetCount
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+3*A_PtrSize), "UPtr", IMMDeviceCollection, "UIntP", Count, "UInt")
Loop % (Count)
{
    ; IMMDeviceCollection::Item
    DllCall(NumGet(NumGet(IMMDeviceCollection+0)+4*A_PtrSize), "UPtr", IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", IMMDevice, "UInt")

    ; IMMDevice::GetId
    DllCall(NumGet(NumGet(IMMDevice+0)+5*A_PtrSize), "UPtr", IMMDevice, "UPtrP", pBuffer, "UInt")
    DeviceID := StrGet(pBuffer, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)

    ; IMMDevice::OpenPropertyStore
    ; 0x0 = STGM_READ
    DllCall(NumGet(NumGet(IMMDevice+0)+4*A_PtrSize), "UPtr", IMMDevice, "UInt", 0x0, "UPtrP", IPropertyStore, "UInt")
    ObjRelease(IMMDevice)

    ; IPropertyStore::GetValue
    VarSetCapacity(PROPVARIANT, A_PtrSize == 4 ? 16 : 24)
    VarSetCapacity(PROPERTYKEY, 20)
    DllCall("Ole32.dll\CLSIDFromString", "Str", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "UPtr", &PROPERTYKEY)
    NumPut(14, &PROPERTYKEY + 16, "UInt")
    DllCall(NumGet(NumGet(IPropertyStore+0)+5*A_PtrSize), "UPtr", IPropertyStore, "UPtr", &PROPERTYKEY, "UPtr", &PROPVARIANT, "UInt")
    DeviceName := StrGet(NumGet(&PROPVARIANT + 8), "UTF-16")    ; LPWSTR PROPVARIANT.pwszVal
    DllCall("Ole32.dll\CoTaskMemFree", "UPtr", NumGet(&PROPVARIANT + 8))    ; LPWSTR PROPVARIANT.pwszVal
    ObjRelease(IPropertyStore)

    ObjRawSet(Devices, DeviceName, DeviceID)
}
ObjRelease(IMMDeviceCollection)


Devices2 := {}
For DeviceName, DeviceID in Devices
    List .= "(" . A_Index . ") " . DeviceName . "`n", ObjRawSet(Devices2, A_Index, DeviceID)
InputBox n,, % List,,,,,,,, 1

MsgBox % Devices2[n]

;IPolicyConfig::SetDefaultEndpoint
IPolicyConfig := ComObjCreate("{870af99c-171d-4f9e-af0d-e63df40c2bc9}", "{F8679F50-850A-41CF-9C72-430F290290C8}") ;00000102-0000-0000-C000-000000000046 00000000-0000-0000-C000-000000000046
R := DllCall(NumGet(NumGet(IPolicyConfig+0)+13*A_PtrSize), "UPtr", IPolicyConfig, "Str", Devices2[n], "UInt", 0, "UInt")
ObjRelease(IPolicyConfig)
MsgBox % Format("0x{:08X}", R)
Note: This method is not documented and may not work in future Windows updates.

I managed to use this script to have it toggle between two audio outputs (speakers and headphones) via

Code: Select all

If audiotoggle = 0
	{
	audioswitcher(2)
	}
	
If audiotoggle = 1
	{
	audioswitcher(3)
	}

audiotoggle := !audiotoggle
return
and having the script as a function:
audioswitcher(devicenum)

My issue is that upon the first run of the script (like after starting up the computer). I have to click the hotkey 2,3 times to make it switch. (After that it toggles as intended).
I think the problem is with my toggle method, where I cant set the initial toggle value of 0 or 1 depending on the audio output during that time.
My question, Is there a way to determine the initial audio output when the script starts?
The DllCall stuff is too high level for me to figure out.

IVWidth
Posts: 17
Joined: 14 Mar 2021, 13:58

Re: Change audio output

Post by IVWidth » 28 Nov 2021, 19:29

The code(s) shown in earlier posts show the use of two hotkeys. If it were me, since the toggle command doesn't mesh well with my brain for some reason, I would just include a hotkey reassignment in my hotkey command.

So pressing the hotkey once switches audio AND changes the hotkey assignment, then doing it again switches it back.

Post Reply

Return to “Ask for Help (v1)”