From e3340a776b1af7818f9392e514adeba94a368e75 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 28 May 2026 10:51:57 -0400 Subject: [PATCH] zebra-report: force G.711/G.722 codec (fix amplitude modem over WebRTC) --- zebra-report/index.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/zebra-report/index.html b/zebra-report/index.html index b564d93..e142f3c 100644 --- a/zebra-report/index.html +++ b/zebra-report/index.html @@ -1150,6 +1150,28 @@ function ensurePC() { if (!carrierOn) throw new Error('start carrier first'); pc = new RTCPeerConnection(RTC_CONFIG); for (const tr of outboundStream.getTracks()) pc.addTrack(tr, outboundStream); + /* Force a memoryless codec (G.711 PCMU/PCMA, else G.722) ahead of Opus. + * Opus is a perceptual codec that re-quantizes in 20ms frames and smears the + * MARK/SPACE bit-edges this modem rides on; G.711 companding is per-sample, + * preserving the carrier envelope like the PulseAudio path. */ + try { + const caps = (RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities('audio')) || null; + if (caps && caps.codecs) { + const rank = (mime) => { + const m = (mime || '').toLowerCase(); + if (m.endsWith('/pcmu')) return 0; + if (m.endsWith('/pcma')) return 1; + if (m.endsWith('/g722')) return 2; + if (m.includes('opus')) return 99; + return 50; + }; + const ordered = caps.codecs.slice().sort((a, b) => rank(a.mimeType) - rank(b.mimeType)); + for (const t of pc.getTransceivers()) { + if (t.setCodecPreferences) { try { t.setCodecPreferences(ordered); } catch (_) {} } + } + logLine('sys', 'codec preference: G.711/G.722 ahead of Opus (clean amplitude)'); + } + } catch (_) {} pc.ontrack = (ev) => { const stream = ev.streams[0] || new MediaStream([ev.track]); attachInboundTrack(stream, ev.track.id);