From 4914182812df83ebb4ea38941f1b6600f21a1762 Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Sun, 9 Aug 2026 12:57:26 -0400 Subject: [PATCH] Respect OpenAL capture availability --- gumble/gumbleopenal/stream.go | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index 80cf30c..24cfbcd 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -103,12 +103,15 @@ func New(client *gumble.Client, inputDevice *string, outputDevice *string, test inputFormat := openal.FormatStereo16 sourceChannels := 2 - idev := openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, inputFormat, uint32(frmsz)) + // Keep several frames in the capture ring so normal scheduler jitter does + // not overflow a PipeWire/Pulse capture stream. + captureBufferSize := uint32(frmsz * 4) + idev := openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, inputFormat, captureBufferSize) if idev == nil { log.Info("OpenAL capture: stereo failed, trying mono") inputFormat = openal.FormatMono16 sourceChannels = 1 - idev = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, inputFormat, uint32(frmsz)) + idev = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, inputFormat, captureBufferSize) } if idev == nil { log.Error("OpenAL capture: failed to open device %q", devName) @@ -618,19 +621,26 @@ func (s *Stream) sourceRoutine(inputDevice *string) { devName = *inputDevice } + reopened := false if frameSize != s.sourceFrameSize { s.deviceSource.CaptureCloseDevice() + reopened = true s.sourceFrameSize = frameSize - s.deviceSource = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, s.sourceFormat, uint32(s.sourceFrameSize)) + captureBufferSize := uint32(s.sourceFrameSize * 4) + s.deviceSource = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, s.sourceFormat, captureBufferSize) if s.deviceSource == nil && s.sourceFormat == openal.FormatStereo16 { s.sourceFormat = openal.FormatMono16 s.sourceChannels = 1 - s.deviceSource = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, s.sourceFormat, uint32(s.sourceFrameSize)) + s.deviceSource = openal.CaptureOpenDevice(devName, gumble.AudioSampleRate, s.sourceFormat, captureBufferSize) } } if s.deviceSource == nil { return } + // Reopening after an interval change creates a stopped capture device. + if reopened { + s.deviceSource.CaptureStart() + } ticker := time.NewTicker(interval) defer ticker.Stop() @@ -649,9 +659,14 @@ func (s *Stream) sourceRoutine(inputDevice *string) { sampleCount := frameSize * s.sourceChannels int16Buffer := make([]int16, sampleCount) - // Capture microphone if available + // alcCaptureSamples requires the requested frames to already be + // available. PipeWire and PulseAudio do not guarantee that a Go + // ticker fires precisely on a capture-frame boundary. hasMicInput := false - buff := s.deviceSource.CaptureSamples(uint32(frameSize)) + var buff []byte + if s.deviceSource.CapturedSamples() >= uint32(frameSize) { + buff = s.deviceSource.CaptureSamples(uint32(frameSize)) + } if len(buff) == sampleCount*2 { hasMicInput = true if micFailed {