feat: added record qso dvk
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
//go:build windows
|
||||
|
||||
// Package audio drives Windows audio endpoints via WASAPI (through go-ole /
|
||||
// go-wca) — pure Go, no CGO, the same COM stack OmniRig already uses. It
|
||||
// powers the Digital Voice Keyer (record/play voice messages to the rig) and
|
||||
// the QSO recorder (rolling-buffer capture saved as WAV).
|
||||
package audio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
"github.com/moutend/go-wca/pkg/wca"
|
||||
)
|
||||
|
||||
// Device is one audio endpoint (a capture input or a render output).
|
||||
type Device struct {
|
||||
ID string `json:"id"` // stable WASAPI endpoint id (persisted)
|
||||
Name string `json:"name"` // friendly name shown in dropdowns
|
||||
Default bool `json:"default"` // is this the system default endpoint
|
||||
}
|
||||
|
||||
// ListInputDevices returns the active capture endpoints — microphones,
|
||||
// line-in, and the soundcard input wired to the rig's audio out ("From Radio").
|
||||
func ListInputDevices() ([]Device, error) { return listEndpoints(wca.ECapture) }
|
||||
|
||||
// ListOutputDevices returns the active render endpoints — speakers and the
|
||||
// soundcard output wired to the rig's mic/data input ("To Radio").
|
||||
func ListOutputDevices() ([]Device, error) { return listEndpoints(wca.ERender) }
|
||||
|
||||
// listEndpoints enumerates active endpoints for a data-flow direction. COM is
|
||||
// thread-affine, so we lock the OS thread and Co(Un)Initialize around the work
|
||||
// — this is a one-shot call from a Wails binding, not a long-lived session.
|
||||
func listEndpoints(flow uint32) (out []Device, err error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if e := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED); e != nil {
|
||||
// 0x1 = S_FALSE → already initialised on this thread, fine.
|
||||
if oe, ok := e.(*ole.OleError); !ok || oe.Code() != 0x00000001 {
|
||||
return nil, fmt.Errorf("CoInitializeEx: %w", e)
|
||||
}
|
||||
}
|
||||
defer ole.CoUninitialize()
|
||||
|
||||
var mmde *wca.IMMDeviceEnumerator
|
||||
if e := wca.CoCreateInstance(wca.CLSID_MMDeviceEnumerator, 0, wca.CLSCTX_ALL,
|
||||
wca.IID_IMMDeviceEnumerator, &mmde); e != nil {
|
||||
return nil, fmt.Errorf("create MMDeviceEnumerator: %w", e)
|
||||
}
|
||||
defer mmde.Release()
|
||||
|
||||
// Record the default endpoint id so the UI can flag it.
|
||||
var defID string
|
||||
var defDev *wca.IMMDevice
|
||||
if e := mmde.GetDefaultAudioEndpoint(flow, wca.EConsole, &defDev); e == nil && defDev != nil {
|
||||
_ = defDev.GetId(&defID)
|
||||
defDev.Release()
|
||||
}
|
||||
|
||||
var coll *wca.IMMDeviceCollection
|
||||
if e := mmde.EnumAudioEndpoints(flow, wca.DEVICE_STATE_ACTIVE, &coll); e != nil {
|
||||
return nil, fmt.Errorf("enum endpoints: %w", e)
|
||||
}
|
||||
defer coll.Release()
|
||||
|
||||
var count uint32
|
||||
if e := coll.GetCount(&count); e != nil {
|
||||
return nil, fmt.Errorf("count endpoints: %w", e)
|
||||
}
|
||||
for i := uint32(0); i < count; i++ {
|
||||
var dev *wca.IMMDevice
|
||||
if coll.Item(i, &dev) != nil || dev == nil {
|
||||
continue
|
||||
}
|
||||
var id string
|
||||
_ = dev.GetId(&id)
|
||||
name := endpointName(dev, id)
|
||||
dev.Release()
|
||||
out = append(out, Device{ID: id, Name: name, Default: id != "" && id == defID})
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// endpointName reads PKEY_Device_FriendlyName, falling back to the raw id.
|
||||
func endpointName(dev *wca.IMMDevice, fallback string) string {
|
||||
var ps *wca.IPropertyStore
|
||||
if dev.OpenPropertyStore(wca.STGM_READ, &ps) != nil || ps == nil {
|
||||
return fallback
|
||||
}
|
||||
defer ps.Release()
|
||||
var pv wca.PROPVARIANT
|
||||
if ps.GetValue(&wca.PKEY_Device_FriendlyName, &pv) != nil {
|
||||
return fallback
|
||||
}
|
||||
if s := pv.String(); s != "" {
|
||||
return s
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
Reference in New Issue
Block a user