Tune mumble bridge to prevent latency drift
This commit is contained in:
@@ -45,6 +45,8 @@ Deploy one container per EVS client so every device appears as its own Mumble us
|
||||
- `VAD_CLOSE_FRAMES`: consecutive silence frames to close gate (default `20`)
|
||||
- `HPF_ENABLED`: enable high-pass/DC blocker to reduce low-frequency rumble (default `true`)
|
||||
- `HPF_CUTOFF_HZ`: high-pass cutoff frequency (default `120.0`)
|
||||
- `TX_BUFFER_MAX_MS`: maximum bridge TX buffer before dropping old audio to stop latency growth (default `800`)
|
||||
- `TX_BUFFER_TARGET_MS`: buffer target after drop (default `300`)
|
||||
|
||||
## Example docker compose service
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ VAD_OPEN_FRAMES = int(os.getenv("VAD_OPEN_FRAMES", "2"))
|
||||
VAD_CLOSE_FRAMES = int(os.getenv("VAD_CLOSE_FRAMES", "20"))
|
||||
HPF_ENABLED = getenv_bool("HPF_ENABLED", True)
|
||||
HPF_CUTOFF_HZ = float(os.getenv("HPF_CUTOFF_HZ", "120.0"))
|
||||
TX_BUFFER_MAX_MS = int(os.getenv("TX_BUFFER_MAX_MS", "800"))
|
||||
TX_BUFFER_TARGET_MS = int(os.getenv("TX_BUFFER_TARGET_MS", "300"))
|
||||
|
||||
|
||||
def _channel_name(ch) -> str:
|
||||
@@ -456,10 +458,14 @@ def run() -> None:
|
||||
hp_prev_y = 0.0
|
||||
out_buffer = bytearray()
|
||||
frame_bytes = int((MUMBLE_SAMPLE_RATE * 2 * FRAME_MS) / 1000) # mono, 16-bit
|
||||
frame_period_s = FRAME_MS / 1000.0
|
||||
next_send_ts = time.monotonic()
|
||||
udp_packets = 0
|
||||
udp_bytes = 0
|
||||
frames_sent = 0
|
||||
send_errors = 0
|
||||
dropped_bytes = 0
|
||||
dropped_frames = 0
|
||||
vad_dropped_packets = 0
|
||||
vad_open = not VAD_ENABLED
|
||||
vad_voice_frames = 0
|
||||
@@ -476,6 +482,7 @@ def run() -> None:
|
||||
rate_state = None
|
||||
hp_prev_x = 0.0
|
||||
hp_prev_y = 0.0
|
||||
next_send_ts = time.monotonic()
|
||||
except Exception:
|
||||
log.exception("mumble connect failed, retrying in %ss", MUMBLE_RECONNECT_SEC)
|
||||
time.sleep(MUMBLE_RECONNECT_SEC)
|
||||
@@ -494,13 +501,15 @@ def run() -> None:
|
||||
if BRIDGE_STATS_INTERVAL_SEC > 0 and (now - stats_t0) >= BRIDGE_STATS_INTERVAL_SEC:
|
||||
dt = max(0.001, now - stats_t0)
|
||||
log.info(
|
||||
"bridge stats: udp_packets=%d udp_bytes=%d udp_kbps=%.1f frames_sent=%d send_errors=%d buffer_bytes=%d vad_open=%s vad_dropped=%d last_from=%s",
|
||||
"bridge stats: udp_packets=%d udp_bytes=%d udp_kbps=%.1f frames_sent=%d send_errors=%d buffer_bytes=%d dropped_frames=%d dropped_bytes=%d vad_open=%s vad_dropped=%d last_from=%s",
|
||||
udp_packets,
|
||||
udp_bytes,
|
||||
(udp_bytes * 8.0 / 1000.0) / dt,
|
||||
frames_sent,
|
||||
send_errors,
|
||||
len(out_buffer),
|
||||
dropped_frames,
|
||||
dropped_bytes,
|
||||
vad_open,
|
||||
vad_dropped_packets,
|
||||
last_udp_from or "-",
|
||||
@@ -509,6 +518,8 @@ def run() -> None:
|
||||
udp_bytes = 0
|
||||
frames_sent = 0
|
||||
send_errors = 0
|
||||
dropped_bytes = 0
|
||||
dropped_frames = 0
|
||||
vad_dropped_packets = 0
|
||||
stats_t0 = now
|
||||
|
||||
@@ -550,11 +561,30 @@ def run() -> None:
|
||||
)
|
||||
out_buffer.extend(processed)
|
||||
|
||||
while len(out_buffer) >= frame_bytes:
|
||||
if TX_BUFFER_MAX_MS > 0:
|
||||
max_bytes = int((MUMBLE_SAMPLE_RATE * 2 * TX_BUFFER_MAX_MS) / 1000)
|
||||
target_bytes = int((MUMBLE_SAMPLE_RATE * 2 * TX_BUFFER_TARGET_MS) / 1000)
|
||||
if target_bytes < frame_bytes:
|
||||
target_bytes = frame_bytes
|
||||
if len(out_buffer) > max_bytes:
|
||||
to_drop = len(out_buffer) - target_bytes
|
||||
# Keep alignment at 16-bit boundaries.
|
||||
to_drop -= to_drop % 2
|
||||
if to_drop > 0:
|
||||
del out_buffer[:to_drop]
|
||||
dropped_bytes += to_drop
|
||||
dropped_frames += to_drop // frame_bytes
|
||||
|
||||
while len(out_buffer) >= frame_bytes and time.monotonic() >= next_send_ts:
|
||||
frame = bytes(out_buffer[:frame_bytes])
|
||||
del out_buffer[:frame_bytes]
|
||||
mumble.sound_output.add_sound(frame)
|
||||
frames_sent += 1
|
||||
next_send_ts += frame_period_s
|
||||
# Prevent "runaway catchup" if scheduler was stalled.
|
||||
now_mono = time.monotonic()
|
||||
if (now_mono - next_send_ts) > (frame_period_s * 5):
|
||||
next_send_ts = now_mono
|
||||
except Exception:
|
||||
send_errors += 1
|
||||
log.exception("audio processing/send failed")
|
||||
|
||||
Reference in New Issue
Block a user