diff --git a/src/tuimble/audio.py b/src/tuimble/audio.py index 1262a6b..9df9e36 100644 --- a/src/tuimble/audio.py +++ b/src/tuimble/audio.py @@ -1,7 +1,8 @@ """Audio capture and playback pipeline. -Handles microphone input, speaker output, and Opus encoding/decoding. -Designed to run alongside the async event loop via callback-based I/O. +Handles microphone input (PCM -> Opus) and speaker output (PCM direct). +pymumble already decodes incoming audio, so the playback path accepts raw +PCM — no decoder needed. The encoder is used only for the capture path. """ from __future__ import annotations @@ -36,7 +37,6 @@ class AudioPipeline: self._playback_queue: queue.Queue[bytes] = queue.Queue(maxsize=50) self._encoder = None - self._decoder = None self._input_stream = None self._output_stream = None self._capturing = False @@ -49,7 +49,6 @@ class AudioPipeline: self._encoder = opuslib.Encoder( self._sample_rate, CHANNELS, opuslib.APPLICATION_VOIP ) - self._decoder = opuslib.Decoder(self._sample_rate, CHANNELS) self._output_stream = sd.RawOutputStream( samplerate=self._sample_rate, @@ -107,12 +106,11 @@ class AudioPipeline: if status: log.warning("playback status: %s", status) try: - data = self._playback_queue.get_nowait() - if self._decoder: - pcm = self._decoder.decode(data, self._frame_size) - outdata[:] = pcm[: len(outdata)] - else: - outdata[:] = b"\x00" * len(outdata) + pcm = self._playback_queue.get_nowait() + n = min(len(pcm), len(outdata)) + outdata[:n] = pcm[:n] + if n < len(outdata): + outdata[n:] = b"\x00" * (len(outdata) - n) except queue.Empty: outdata[:] = b"\x00" * len(outdata) @@ -123,9 +121,9 @@ class AudioPipeline: except queue.Empty: return None - def queue_playback(self, opus_data: bytes): - """Queue encoded audio data for playback.""" + def queue_playback(self, pcm_data: bytes): + """Queue raw PCM data for playback (16-bit, mono, 48kHz).""" try: - self._playback_queue.put_nowait(opus_data) + self._playback_queue.put_nowait(pcm_data) except queue.Full: pass