diff --git a/src/tuimble/app.py b/src/tuimble/app.py index 92b591e..ecb2b52 100644 --- a/src/tuimble/app.py +++ b/src/tuimble/app.py @@ -342,7 +342,7 @@ class TuimbleApp(App): def _audio_send_loop(self) -> None: """Poll capture queue and send encoded frames to server.""" while self._client.connected: - frame = self._audio.get_encoded_frame() + frame = self._audio.get_capture_frame() if frame is not None: self._client.send_audio(frame) else: diff --git a/src/tuimble/audio.py b/src/tuimble/audio.py index 9df9e36..7930d78 100644 --- a/src/tuimble/audio.py +++ b/src/tuimble/audio.py @@ -1,8 +1,8 @@ """Audio capture and playback pipeline. -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. +Pure PCM — pymumble handles Opus encode/decode internally. +Capture path: mic -> raw PCM -> queue -> pymumble (encodes + sends). +Playback path: pymumble (decodes) -> raw PCM -> queue -> speakers. """ from __future__ import annotations @@ -36,20 +36,14 @@ class AudioPipeline: self._capture_queue: queue.Queue[bytes] = queue.Queue(maxsize=50) self._playback_queue: queue.Queue[bytes] = queue.Queue(maxsize=50) - self._encoder = None self._input_stream = None self._output_stream = None self._capturing = False def start(self): - """Initialize codec and open audio streams.""" - import opuslib + """Open audio streams.""" import sounddevice as sd - self._encoder = opuslib.Encoder( - self._sample_rate, CHANNELS, opuslib.APPLICATION_VOIP - ) - self._output_stream = sd.RawOutputStream( samplerate=self._sample_rate, channels=CHANNELS, @@ -94,10 +88,9 @@ class AudioPipeline: """Called by sounddevice when input data is available.""" if status: log.warning("capture status: %s", status) - if self._capturing and self._encoder: + if self._capturing: try: - encoded = self._encoder.encode(bytes(indata), self._frame_size) - self._capture_queue.put_nowait(encoded) + self._capture_queue.put_nowait(bytes(indata)) except queue.Full: pass @@ -114,8 +107,8 @@ class AudioPipeline: except queue.Empty: outdata[:] = b"\x00" * len(outdata) - def get_encoded_frame(self) -> bytes | None: - """Retrieve next encoded frame for transmission.""" + def get_capture_frame(self) -> bytes | None: + """Retrieve next captured PCM frame for transmission.""" try: return self._capture_queue.get_nowait() except queue.Empty: diff --git a/src/tuimble/client.py b/src/tuimble/client.py index 64a8725..b1b43dd 100644 --- a/src/tuimble/client.py +++ b/src/tuimble/client.py @@ -170,10 +170,10 @@ class MumbleClient: ch = self._mumble.channels[self._mumble.users.myself["channel_id"]] ch.send_text_message(message) - def send_audio(self, opus_data: bytes): - """Send encoded audio to the server.""" + def send_audio(self, pcm_data: bytes): + """Send PCM audio to the server (pymumble encodes to Opus).""" if self._mumble and self._connected: - self._mumble.sound_output.add_sound(opus_data) + self._mumble.sound_output.add_sound(pcm_data) def join_channel(self, channel_id: int): """Move to a different channel."""