-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example: muting microphone #4
Comments
I figured it out. package main
import (
"fmt"
"github.com/go-ole/go-ole"
"github.com/moutend/go-wca/pkg/wca"
)
func main() {
if err := volumn(); err != nil {
panic(err)
}
}
func volumn() (err error) {
if err = ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
return
}
defer ole.CoUninitialize()
var mmde *wca.IMMDeviceEnumerator
if err = wca.CoCreateInstance(wca.CLSID_MMDeviceEnumerator, 0, wca.CLSCTX_ALL, wca.IID_IMMDeviceEnumerator, &mmde); err != nil {
return
}
defer mmde.Release()
var mmd *wca.IMMDevice
if err = mmde.GetDefaultAudioEndpoint(wca.ECapture, wca.EConsole, &mmd); err != nil {
return
}
defer mmd.Release()
/*var ps *wca.IPropertyStore
if err = mmd.OpenPropertyStore(wca.STGM_READ, &ps); err != nil {
return
}
defer ps.Release()
var pv wca.PROPVARIANT
if err = ps.GetValue(&wca.PKEY_Device_FriendlyName, &pv); err != nil {
return
}
fmt.Printf("%s\n", pv.String())*/
var aev *wca.IAudioEndpointVolume
if err = mmd.Activate(wca.IID_IAudioEndpointVolume, wca.CLSCTX_ALL, nil, &aev); err != nil {
return
}
defer aev.Release()
var mute bool
if err = aev.GetMute(&mute); err != nil {
return
}
if err = aev.SetMute(!mute, nil); err != nil {
return
}
fmt.Printf("mute: %v\n", !mute)
return
} Notice that we are using ECapture instead of ERender |
It would be nice if an example were created and published with the On a side note: in case having two example is useful for anyone coming across this, I recently implemented something like this to be used while streaming in OBS, but I needed to be specific as to which microphone was being muted. Here's how I went about it, which is definitely far more code and complication than @zllovesuki 's above. package main
import (
"fmt"
"log"
"time"
"github.com/go-ole/go-ole"
"github.com/moutend/go-wca/pkg/wca"
)
type micDevice struct {
id string
name string
}
type micState struct {
muted *bool
volume *float32
}
func (m *micState) IsEqual(other *micState) bool {
if m == nil && other == nil { // both are nil, which is equal
return true
}
// one is nil, but not both
if m == nil || other == nil {
return false
}
// one has nil muted, but the other doesn't
if m.muted == nil && other.muted != nil {
return false
}
if m.muted != nil && other.muted == nil {
return false
}
// one has nil volume, but the other doesn't
if m.volume == nil && other.volume != nil {
return false
}
if m.volume != nil && other.volume == nil {
return false
}
// both are not nil, so compare the values and return the result
return *m.muted == *other.muted && *m.volume == *other.volume
}
// IsMuted returns true if the mic is muted or the volume is 0 (nil values are not considered muted)
func (m *micState) IsMuted() bool {
if m == nil {
return false
}
return (m.muted != nil && *m.muted) || (m.volume != nil && *m.volume == 0)
}
// IsUnmuted returns true if the mic is not muted or the volume is greater than 0 (nil values are not considered unmuted)
func (m *micState) IsUnmuted() bool {
if m == nil {
return false
}
return (m.muted != nil && !*m.muted) || (m.volume != nil && *m.volume > 0)
}
// IsUndetermined returns true if the mic state is undetermined (nil values)
func (m *micState) IsUndetermined() bool {
return m == nil || (m.muted == nil && m.volume == nil)
}
func wcaECaptureDeviceInfo(matcher func(micDevice) bool, callback func(micDevice, micState) *micState) error {
// Initialize COM library
ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
defer ole.CoUninitialize()
// Initialize Windows Core Audio
var enumerator *wca.IMMDeviceEnumerator
if err := wca.CoCreateInstance(wca.CLSID_MMDeviceEnumerator, 0, wca.CLSCTX_ALL, wca.IID_IMMDeviceEnumerator, &enumerator); err != nil {
return fmt.Errorf("failed to create device enumerator: %v", err)
}
defer enumerator.Release()
// Enumerate audio endpoint devices
var deviceCollection *wca.IMMDeviceCollection
if err := enumerator.EnumAudioEndpoints(wca.ECapture, wca.DEVICE_STATE_ACTIVE, &deviceCollection); err != nil {
return fmt.Errorf("failed to enumerate audio endpoints: %v", err)
}
defer deviceCollection.Release()
// Get device count
var count uint32
if err := deviceCollection.GetCount(&count); err != nil {
return fmt.Errorf("failed to get device count: %v", err)
}
// Iterate over devices
for i := uint32(0); i < count; i++ {
var item *wca.IMMDevice
if err := deviceCollection.Item(i, &item); err != nil {
return fmt.Errorf("failed to get device item: %v", err)
}
defer item.Release()
// Get the device ID
var deviceId string
if err := item.GetId(&deviceId); err != nil {
return fmt.Errorf("failed to get device ID: %v", err)
}
// Open the property store
var propertyStore *wca.IPropertyStore
if err := item.OpenPropertyStore(wca.STGM_READ, &propertyStore); err != nil {
return fmt.Errorf("failed to open property store: %v", err)
}
defer propertyStore.Release()
// Get the friendly name of the device
var propVariant wca.PROPVARIANT
if err := propertyStore.GetValue(&wca.PKEY_Device_FriendlyName, &propVariant); err != nil {
return fmt.Errorf("failed to get friendly name: %v", err)
}
deviceName := propVariant.String()
propVariant.Clear()
// Check if the device is the one we're looking for
if matcher(micDevice{id: deviceId, name: deviceName}) {
// log.Printf("Found Windows Core Audio device: %s\n", deviceName)
// Get the IAudioEndpointVolume interface
var audioEndpointVolume *wca.IAudioEndpointVolume
if err := item.Activate(wca.IID_IAudioEndpointVolume, wca.CLSCTX_ALL, nil, &audioEndpointVolume); err != nil {
return fmt.Errorf("failed to activate audio endpoint volume: %v", err)
}
defer audioEndpointVolume.Release()
getMicState := func(audioEndpointVolume *wca.IAudioEndpointVolume) *micState {
// Build the micState object, starting with nil values to indicate undetermined state
state := &micState{}
// Get the mute state
for j := 0; j < 5; j++ {
var muted bool
if err := audioEndpointVolume.GetMute(&muted); err != nil {
log.Printf("failed to get mute state: %v", err)
} else {
state.muted = &muted
// log.Printf("Mute state: %v\n", muted)
break
}
time.Sleep(100 * time.Millisecond)
}
// Get the volume level
for j := 0; j < 5; j++ {
var volume float32
if err := audioEndpointVolume.GetMasterVolumeLevelScalar(&volume); err != nil {
log.Printf("failed to get volume level: %v", err)
} else {
state.volume = &volume
// log.Printf("Volume level: %v\n", volume)
break
}
time.Sleep(100 * time.Millisecond)
}
return state
}
setMicState := func(audioEndpointVolume *wca.IAudioEndpointVolume, state *micState) {
if state == nil {
return
}
// Existing state
currentState := getMicState(audioEndpointVolume)
// Set the mute state, if necessary
if state.muted != nil && currentState.muted != state.muted {
log.Printf("Setting mute state to %v\n", *state.muted)
if err := audioEndpointVolume.SetMute(*state.muted, nil); err != nil {
log.Printf("Failed to set mute state: %v\n", err)
// } else {
// log.Printf("Set mute state: %v\n", *state.muted)
}
}
// Set the volume level, if necessary
if state.volume != nil && currentState.volume != state.volume {
log.Printf("Setting volume level to %v\n", *state.volume)
if err := audioEndpointVolume.SetMasterVolumeLevelScalar(*state.volume, nil); err != nil {
log.Printf("Failed to set volume level: %v\n", err)
// } else {
// log.Printf("Set volume level: %v\n", *state.volume)
}
}
}
// Get the initial state
state := getMicState(audioEndpointVolume)
// Call the callback, if provided
if callback != nil {
dev := micDevice{
id: deviceId,
name: deviceName,
}
newState := callback(dev, *state)
// Set the new state, if provided
if newState != nil {
updateMicState := callback(dev, *state)
if updateMicState != nil {
// the callback asked for a change, so update the state
setMicState(audioEndpointVolume, updateMicState)
}
}
}
}
}
return nil
}
func observeMicState(name string, callback func([]micDevice, []micState)) chan bool {
if name == "" {
return nil
}
log.Printf("Observing microphone state: %s\n", name)
done := make(chan bool)
deviceMatcherByName := func(name string) func(micDevice) bool {
return func(device micDevice) bool {
return device.name == name
}
}
captureMicState := func(matchedDevices *[]micDevice, capturedStates *[]micState) func(micDevice, micState) *micState {
return func(device micDevice, state micState) *micState {
*matchedDevices = append(*matchedDevices, device)
*capturedStates = append(*capturedStates, state)
// no changes requested, so return nil
return nil
}
}
captureDeviceInfoOnce := func(name string) ([]micDevice, []micState, error) {
states := make([]micState, 0)
devices := make([]micDevice, 0)
err := wcaECaptureDeviceInfo(deviceMatcherByName(name), captureMicState(&devices, &states))
if err != nil {
log.Printf("Failed to get state: %v\n", err)
}
return devices, states, err
}
go func(name string, callback func([]micDevice, []micState)) {
devices, states, err := captureDeviceInfoOnce(name)
if err != nil {
log.Printf("Failed to get initial state: %v\n", err)
}
if len(devices) == 0 || len(states) == 0 {
log.Printf("No devices found for name: %s\n", name)
} else {
callback(devices, states)
}
for {
select {
case <-done:
return
case <-time.After(1 * time.Second):
devicesNow, statesNow, err := captureDeviceInfoOnce(name)
if err != nil {
log.Printf("Failed to get state: %v\n", err)
}
if len(devicesNow) != len(statesNow) {
log.Printf("Mismatched devices and states: %d != %d\n", len(devicesNow), len(statesNow))
continue
}
// turn the devices & states into maps for easier comparison
historyDevices := make(map[string]micDevice)
for i := 0; i < len(devices); i++ {
historyDevices[devices[i].id] = devices[i]
}
historyStates := make(map[string]micState)
for i := 0; i < len(states); i++ {
historyStates[devices[i].id] = states[i]
}
// turn the devicesNow & statesNow into maps for easier comparison
devicesNowMap := make(map[string]micDevice)
for i := 0; i < len(devicesNow); i++ {
devicesNowMap[devicesNow[i].id] = devicesNow[i]
}
statesNowMap := make(map[string]micState)
for i := 0; i < len(statesNow); i++ {
statesNowMap[devicesNow[i].id] = statesNow[i]
}
// find the devices and states that have changed
changedDevices := make([]micDevice, 0)
changedStates := make([]micState, 0)
// check if the state has changed
for i := 0; i < len(devicesNow); i++ {
historyState, ok := historyStates[devicesNow[i].id]
if (ok && !statesNow[i].IsEqual(&historyState)) || (!ok) {
// state has changed or is new
log.Printf("State changed for %s\n", devicesNow[i].name)
historyStates[devicesNow[i].id] = statesNow[i]
changedDevices = append(changedDevices, devicesNow[i])
changedStates = append(changedStates, statesNow[i])
}
}
// check if a device has been removed
for id, device := range historyDevices {
if _, ok := devicesNowMap[id]; !ok {
// device has been removed
log.Printf("Device removed: %s\n", device.name)
delete(historyDevices, id)
delete(historyStates, id)
changedDevices = append(changedDevices, device)
changedStates = append(changedStates, micState{})
}
}
// check if a device has been added/removed
if len(changedDevices) > 0 {
// update the history from the map
devices = make([]micDevice, 0)
states = make([]micState, 0)
for id, state := range historyStates {
devices = append(devices, historyDevices[id])
states = append(states, state)
}
// call the callback with the changed devices and states
callback(changedDevices, changedStates)
}
}
}
}(name, callback)
return done
}
func toggleMicMute(name string) {
if name == "" {
return
}
// log.Printf("Toggling microphone mute: %s\n", name)
// the device matcher logic, which simply uses the device's display
// name as shown in the Windows UI, but there's no reason you couldn't
// use completely different matching logic
deviceMatcherByName := func(name string) func(micDevice) bool {
return func(device micDevice) bool {
return device.name == name
}
}
// this performs the actual mic state toggle, which is used in
toggleMicMute := func(device micDevice, state micState) *micState {
log.Printf("Toggling mute for: %s\n", device.name)
if state.muted != nil {
// log.Printf("Current mute state: %v\n", *state.muted)
wantState := !(*state.muted)
// log.Printf("Wanted mute state: %v\n", wantState)
return &micState{muted: &wantState}
}
return nil
}
// how the `wcaECaptureDeviceInfo` function is used to locate the mic,
// including the matcher function and the callback that performs the toggle
_ = wcaECaptureDeviceInfo(deviceMatcherByName(name), toggleMicMute)
} And here's how it is used: // provide the name as seen in the Windows UI
// (see comments in the function above for explanation)
toggleMicMute("Microphone (Yeti GX)") |
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?1.14
What operating system and processor architecture are you using (
go env
)?Windows 10 64-bit 2004
Hello, I came across your library and I was wondering if you have an example of muting the microphone. I see that the examples are all about output devices but none are about input devices. Thank you!
The text was updated successfully, but these errors were encountered: