moutend / go-wca

Pure golang bindings for Windows Core Audio API. The `cgo` is not required.
MIT License
108 stars 28 forks source link

Add API to set default audio output device #7

Open ThiefMaster opened 3 years ago

ThiefMaster commented 3 years ago

There is an undocumented COM API called IPolicyConfig which allows to do nice things like changing the default audio device used by Windows. An example tool (which works fine) can be found e.g. here: https://github.com/DanStevens/AudioEndPointController/tree/master/EndPointController

Is there any chance you could add those APIs to this library as well?

ThiefMaster commented 3 years ago

This works fine for me - just leaving it here in case someone else wants to use it :)

package main

import (
    "syscall"
    "unsafe"

    "github.com/go-ole/go-ole"
    "github.com/moutend/go-wca/pkg/wca"
)

type IPolicyConfigVista struct {
    ole.IUnknown
}

type IPolicyConfigVistaVtbl struct {
    ole.IUnknownVtbl
    GetMixFormat          uintptr
    GetDeviceFormat       uintptr
    SetDeviceFormat       uintptr
    GetProcessingPeriod   uintptr
    SetProcessingPeriod   uintptr
    GetShareMode          uintptr
    SetShareMode          uintptr
    GetPropertyValue      uintptr
    SetPropertyValue      uintptr
    SetDefaultEndpoint    uintptr
    SetEndpointVisibility uintptr
}

func (v *IPolicyConfigVista) VTable() *IPolicyConfigVistaVtbl {
    return (*IPolicyConfigVistaVtbl)(unsafe.Pointer(v.RawVTable))
}

func (v *IPolicyConfigVista) SetDefaultEndpoint(deviceID string, eRole wca.ERole) (err error) {
    err = pcvSetDefaultEndpoint(v, deviceID, eRole)
    return
}

func pcvSetDefaultEndpoint(pcv *IPolicyConfigVista, deviceID string, eRole wca.ERole) (err error) {
    var ptr *uint16
    if ptr, err = syscall.UTF16PtrFromString(deviceID); err != nil {
        return
    }
    hr, _, _ := syscall.Syscall(
        pcv.VTable().SetDefaultEndpoint,
        3,
        uintptr(unsafe.Pointer(pcv)),
        uintptr(unsafe.Pointer(ptr)),
        uintptr(uint32(eRole)))
    if hr != 0 {
        err = ole.NewError(hr)
    }
    return
}
Silenc3IsGold3n commented 3 years ago

Thanks, just implemented this and its working on my end as well

syllith commented 1 year ago

Can you please use this in a complete example? I'm having a hard time understanding how to use this. Thank you

gluek commented 10 months ago

@syllith if it still helps here is an example which works for me in combination with the code provided by ThiefMaster.

func SetAudioDeviceByID(deviceID string) {
    GUID_IPolicyConfigVista := ole.NewGUID("{568b9108-44bf-40b4-9006-86afe5b5a620}")
    GUID_CPolicyConfigVistaClient := ole.NewGUID("{294935CE-F637-4E7C-A41B-AB255460B862}")
    var policyConfig *IPolicyConfigVista

    if err = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); err != nil {
        panic(err)
    }
    defer ole.CoUninitialize()

    if err = wca.CoCreateInstance(GUID_CPolicyConfigVistaClient, 0, wca.CLSCTX_ALL, GUID_IPolicyConfigVista, &policyConfig); err != nil {
        panic(err)
    }
    defer policyConfig.Release()

    if err = policyConfig.SetDefaultEndpoint(deviceID, wca.EConsole); err != nil {
        panic(err)
    }
}