diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index 92f8a8c..86b6b34 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -17,6 +17,21 @@ import ( "git.stormux.org/storm/barnard/noise" ) +func deviceName(name string) string { + if name == "" { + return "default" + } + return name +} + +func openInputDeviceError(name string, format openal.Format) error { + return fmt.Errorf("%w: could not open capture device %q (format=%v, rate=%d)", ErrInputDevice, deviceName(name), format, gumble.AudioSampleRate) +} + +func openOutputDeviceError(name string) error { + return fmt.Errorf("%w: could not open playback device %q", ErrOutputDevice, deviceName(name)) +} + // NoiseProcessor interface for noise suppression type NoiseProcessor interface { ProcessSamples(samples []int16) @@ -109,11 +124,11 @@ func New(client *gumble.Client, inputDevice *string, outputDevice *string, test } if idev == nil { log.Error("OpenAL capture: failed to open device %q", devName) - return nil, ErrInputDevice + return nil, openInputDeviceError(devName, inputFormat) } if err := idev.Err(); err != nil { idev.CaptureCloseDevice() - return nil, fmt.Errorf("%w: %v", ErrInputDevice, err) + return nil, fmt.Errorf("%w: capture device %q: %v", ErrInputDevice, deviceName(devName), err) } log.Info("OpenAL capture: opened device %q format=%v channels=%d", devName, inputFormat, sourceChannels) @@ -124,12 +139,12 @@ func New(client *gumble.Client, inputDevice *string, outputDevice *string, test odev := openal.OpenDevice(outName) if odev == nil { idev.CaptureCloseDevice() - return nil, ErrOutputDevice + return nil, openOutputDeviceError(outName) } if err := odev.Err(); err != nil { idev.CaptureCloseDevice() odev.CloseDevice() - return nil, fmt.Errorf("%w: %v", ErrOutputDevice, err) + return nil, fmt.Errorf("%w: playback device %q: %v", ErrOutputDevice, deviceName(outName), err) } if test { diff --git a/gumble/gumbleopenal/stream_regression_test.go b/gumble/gumbleopenal/stream_regression_test.go index 4fc4660..996f930 100644 --- a/gumble/gumbleopenal/stream_regression_test.go +++ b/gumble/gumbleopenal/stream_regression_test.go @@ -1,11 +1,30 @@ package gumbleopenal -import "testing" +import ( + "errors" + "strings" + "testing" + + "git.stormux.org/storm/barnard/gumble/go-openal/openal" +) // Regression: audio cleanup could send a final render command after Destroy // had closed renderCh, panicking instead of safely discarding that work. // Regression: StopSource returned before the capture worker ended, allowing // Destroy to close the device while that worker still used it. +// Regression: OpenAL returned only a generic input/output error, hiding the +// actual configured device that a user must correct. +func TestDeviceOpenErrorsIncludeConfiguredDevice(t *testing.T) { + input := openInputDeviceError("virtual_mic.monitor", openal.FormatMono16) + if !errors.Is(input, ErrInputDevice) || !strings.Contains(input.Error(), "virtual_mic.monitor") { + t.Fatalf("input error %q", input) + } + output := openOutputDeviceError("") + if !errors.Is(output, ErrOutputDevice) || !strings.Contains(output.Error(), "default") { + t.Fatalf("output error %q", output) + } +} + func TestStopSourceWaitsForWorker(t *testing.T) { stop, done := make(chan bool), make(chan struct{}) s := &Stream{sourceStop: stop, sourceDone: done}