audio: replace struct pack/unpack with array module in _apply_gain
Eliminates format string construction and intermediate tuple/list allocations. Also drains stale frames from queues on stop().
This commit is contained in:
@@ -7,9 +7,9 @@ Playback path: pymumble (decodes) -> raw PCM -> queue -> speakers.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
import logging
|
||||
import queue
|
||||
import struct
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,13 +21,13 @@ DTYPE = "int16"
|
||||
|
||||
def _apply_gain(pcm: bytes, gain: float) -> bytes:
|
||||
"""Scale int16 PCM samples by gain factor with clipping."""
|
||||
n = len(pcm) // 2
|
||||
if n == 0:
|
||||
if len(pcm) < 2:
|
||||
return pcm
|
||||
fmt = f"<{n}h"
|
||||
samples = struct.unpack(fmt, pcm[: n * 2])
|
||||
scaled = [max(-32768, min(32767, int(s * gain))) for s in samples]
|
||||
return struct.pack(fmt, *scaled)
|
||||
samples = array.array("h")
|
||||
samples.frombytes(pcm[: len(pcm) & ~1])
|
||||
for i in range(len(samples)):
|
||||
samples[i] = max(-32768, min(32767, int(samples[i] * gain)))
|
||||
return samples.tobytes()
|
||||
|
||||
|
||||
class AudioPipeline:
|
||||
@@ -82,13 +82,19 @@ class AudioPipeline:
|
||||
log.info("audio pipeline started (rate=%d)", self._sample_rate)
|
||||
|
||||
def stop(self):
|
||||
"""Close audio streams."""
|
||||
"""Close audio streams and drain stale frames."""
|
||||
for stream in (self._input_stream, self._output_stream):
|
||||
if stream is not None:
|
||||
stream.stop()
|
||||
stream.close()
|
||||
self._input_stream = None
|
||||
self._output_stream = None
|
||||
for q in (self._capture_queue, self._playback_queue):
|
||||
while not q.empty():
|
||||
try:
|
||||
q.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
log.info("audio pipeline stopped")
|
||||
|
||||
@property
|
||||
@@ -154,9 +160,15 @@ class AudioPipeline:
|
||||
except queue.Empty:
|
||||
outdata[:] = b"\x00" * len(outdata)
|
||||
|
||||
def get_capture_frame(self) -> bytes | None:
|
||||
"""Retrieve next captured PCM frame for transmission."""
|
||||
def get_capture_frame(self, timeout: float = 0.0) -> bytes | None:
|
||||
"""Retrieve next captured PCM frame for transmission.
|
||||
|
||||
Args:
|
||||
timeout: Seconds to wait for a frame. 0 returns immediately.
|
||||
"""
|
||||
try:
|
||||
if timeout > 0:
|
||||
return self._capture_queue.get(timeout=timeout)
|
||||
return self._capture_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user