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`)
|
- `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_ENABLED`: enable high-pass/DC blocker to reduce low-frequency rumble (default `true`)
|
||||||
- `HPF_CUTOFF_HZ`: high-pass cutoff frequency (default `120.0`)
|
- `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
|
## 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"))
|
VAD_CLOSE_FRAMES = int(os.getenv("VAD_CLOSE_FRAMES", "20"))
|
||||||
HPF_ENABLED = getenv_bool("HPF_ENABLED", True)
|
HPF_ENABLED = getenv_bool("HPF_ENABLED", True)
|
||||||
HPF_CUTOFF_HZ = float(os.getenv("HPF_CUTOFF_HZ", "120.0"))
|
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:
|
def _channel_name(ch) -> str:
|
||||||
@@ -456,10 +458,14 @@ def run() -> None:
|
|||||||
hp_prev_y = 0.0
|
hp_prev_y = 0.0
|
||||||
out_buffer = bytearray()
|
out_buffer = bytearray()
|
||||||
frame_bytes = int((MUMBLE_SAMPLE_RATE * 2 * FRAME_MS) / 1000) # mono, 16-bit
|
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_packets = 0
|
||||||
udp_bytes = 0
|
udp_bytes = 0
|
||||||
frames_sent = 0
|
frames_sent = 0
|
||||||
send_errors = 0
|
send_errors = 0
|
||||||
|
dropped_bytes = 0
|
||||||
|
dropped_frames = 0
|
||||||
vad_dropped_packets = 0
|
vad_dropped_packets = 0
|
||||||
vad_open = not VAD_ENABLED
|
vad_open = not VAD_ENABLED
|
||||||
vad_voice_frames = 0
|
vad_voice_frames = 0
|
||||||
@@ -476,6 +482,7 @@ def run() -> None:
|
|||||||
rate_state = None
|
rate_state = None
|
||||||
hp_prev_x = 0.0
|
hp_prev_x = 0.0
|
||||||
hp_prev_y = 0.0
|
hp_prev_y = 0.0
|
||||||
|
next_send_ts = time.monotonic()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("mumble connect failed, retrying in %ss", MUMBLE_RECONNECT_SEC)
|
log.exception("mumble connect failed, retrying in %ss", MUMBLE_RECONNECT_SEC)
|
||||||
time.sleep(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:
|
if BRIDGE_STATS_INTERVAL_SEC > 0 and (now - stats_t0) >= BRIDGE_STATS_INTERVAL_SEC:
|
||||||
dt = max(0.001, now - stats_t0)
|
dt = max(0.001, now - stats_t0)
|
||||||
log.info(
|
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_packets,
|
||||||
udp_bytes,
|
udp_bytes,
|
||||||
(udp_bytes * 8.0 / 1000.0) / dt,
|
(udp_bytes * 8.0 / 1000.0) / dt,
|
||||||
frames_sent,
|
frames_sent,
|
||||||
send_errors,
|
send_errors,
|
||||||
len(out_buffer),
|
len(out_buffer),
|
||||||
|
dropped_frames,
|
||||||
|
dropped_bytes,
|
||||||
vad_open,
|
vad_open,
|
||||||
vad_dropped_packets,
|
vad_dropped_packets,
|
||||||
last_udp_from or "-",
|
last_udp_from or "-",
|
||||||
@@ -509,6 +518,8 @@ def run() -> None:
|
|||||||
udp_bytes = 0
|
udp_bytes = 0
|
||||||
frames_sent = 0
|
frames_sent = 0
|
||||||
send_errors = 0
|
send_errors = 0
|
||||||
|
dropped_bytes = 0
|
||||||
|
dropped_frames = 0
|
||||||
vad_dropped_packets = 0
|
vad_dropped_packets = 0
|
||||||
stats_t0 = now
|
stats_t0 = now
|
||||||
|
|
||||||
@@ -550,11 +561,30 @@ def run() -> None:
|
|||||||
)
|
)
|
||||||
out_buffer.extend(processed)
|
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])
|
frame = bytes(out_buffer[:frame_bytes])
|
||||||
del out_buffer[:frame_bytes]
|
del out_buffer[:frame_bytes]
|
||||||
mumble.sound_output.add_sound(frame)
|
mumble.sound_output.add_sound(frame)
|
||||||
frames_sent += 1
|
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:
|
except Exception:
|
||||||
send_errors += 1
|
send_errors += 1
|
||||||
log.exception("audio processing/send failed")
|
log.exception("audio processing/send failed")
|
||||||
|
|||||||
Reference in New Issue
Block a user