audio: remove opus codec, pass raw pcm to pymumble
pymumble handles opus encode/decode internally. The pipeline is now pure PCM on both capture and playback paths.
This commit is contained in:
@@ -342,7 +342,7 @@ class TuimbleApp(App):
|
|||||||
def _audio_send_loop(self) -> None:
|
def _audio_send_loop(self) -> None:
|
||||||
"""Poll capture queue and send encoded frames to server."""
|
"""Poll capture queue and send encoded frames to server."""
|
||||||
while self._client.connected:
|
while self._client.connected:
|
||||||
frame = self._audio.get_encoded_frame()
|
frame = self._audio.get_capture_frame()
|
||||||
if frame is not None:
|
if frame is not None:
|
||||||
self._client.send_audio(frame)
|
self._client.send_audio(frame)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Audio capture and playback pipeline.
|
"""Audio capture and playback pipeline.
|
||||||
|
|
||||||
Handles microphone input (PCM -> Opus) and speaker output (PCM direct).
|
Pure PCM — pymumble handles Opus encode/decode internally.
|
||||||
pymumble already decodes incoming audio, so the playback path accepts raw
|
Capture path: mic -> raw PCM -> queue -> pymumble (encodes + sends).
|
||||||
PCM — no decoder needed. The encoder is used only for the capture path.
|
Playback path: pymumble (decodes) -> raw PCM -> queue -> speakers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -36,20 +36,14 @@ class AudioPipeline:
|
|||||||
self._capture_queue: queue.Queue[bytes] = queue.Queue(maxsize=50)
|
self._capture_queue: queue.Queue[bytes] = queue.Queue(maxsize=50)
|
||||||
self._playback_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._input_stream = None
|
||||||
self._output_stream = None
|
self._output_stream = None
|
||||||
self._capturing = False
|
self._capturing = False
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Initialize codec and open audio streams."""
|
"""Open audio streams."""
|
||||||
import opuslib
|
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
|
||||||
self._encoder = opuslib.Encoder(
|
|
||||||
self._sample_rate, CHANNELS, opuslib.APPLICATION_VOIP
|
|
||||||
)
|
|
||||||
|
|
||||||
self._output_stream = sd.RawOutputStream(
|
self._output_stream = sd.RawOutputStream(
|
||||||
samplerate=self._sample_rate,
|
samplerate=self._sample_rate,
|
||||||
channels=CHANNELS,
|
channels=CHANNELS,
|
||||||
@@ -94,10 +88,9 @@ class AudioPipeline:
|
|||||||
"""Called by sounddevice when input data is available."""
|
"""Called by sounddevice when input data is available."""
|
||||||
if status:
|
if status:
|
||||||
log.warning("capture status: %s", status)
|
log.warning("capture status: %s", status)
|
||||||
if self._capturing and self._encoder:
|
if self._capturing:
|
||||||
try:
|
try:
|
||||||
encoded = self._encoder.encode(bytes(indata), self._frame_size)
|
self._capture_queue.put_nowait(bytes(indata))
|
||||||
self._capture_queue.put_nowait(encoded)
|
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -114,8 +107,8 @@ class AudioPipeline:
|
|||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
outdata[:] = b"\x00" * len(outdata)
|
outdata[:] = b"\x00" * len(outdata)
|
||||||
|
|
||||||
def get_encoded_frame(self) -> bytes | None:
|
def get_capture_frame(self) -> bytes | None:
|
||||||
"""Retrieve next encoded frame for transmission."""
|
"""Retrieve next captured PCM frame for transmission."""
|
||||||
try:
|
try:
|
||||||
return self._capture_queue.get_nowait()
|
return self._capture_queue.get_nowait()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
|||||||
@@ -170,10 +170,10 @@ class MumbleClient:
|
|||||||
ch = self._mumble.channels[self._mumble.users.myself["channel_id"]]
|
ch = self._mumble.channels[self._mumble.users.myself["channel_id"]]
|
||||||
ch.send_text_message(message)
|
ch.send_text_message(message)
|
||||||
|
|
||||||
def send_audio(self, opus_data: bytes):
|
def send_audio(self, pcm_data: bytes):
|
||||||
"""Send encoded audio to the server."""
|
"""Send PCM audio to the server (pymumble encodes to Opus)."""
|
||||||
if self._mumble and self._connected:
|
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):
|
def join_channel(self, channel_id: int):
|
||||||
"""Move to a different channel."""
|
"""Move to a different channel."""
|
||||||
|
|||||||
Reference in New Issue
Block a user