RTCPeerConnection runs the browser’s real audio pipeline: echo cancellation,
gain control, noise suppression, and the Opus encoder. An
RTCRtpScriptTransform intercepts the encoded Opus frames before
packetization. The SDK hands those frames to the MoQT publication and sends
them over QUIC/WebTransport. This page explains the tap in depth: where it
runs, why the older API is gone, and exactly how each frame moves from the
encoder to the wire.
For the one-call developer surface, see
One-Line WebRTC Diversion and
Browser Audio Capture.
This page is the mechanism underneath both.
The transform runs in a Worker — and only there
RTCRtpScriptTransform is the W3C Encoded Transform API. You construct it on
the main thread and attach it to an RTCRtpSender, but the transform callback
itself runs in a dedicated Worker. The SDK ships exactly one code path for
tapping encoded frames, and it is this Worker-based transform.
The older, Chromium-only
RTCRtpSender.createEncodedStreams() exposed the
encoded stream on the main thread. Current browser engines removed it,
and the SDK removed its legacy branch for it too. The Worker transform is the
only encoded-frame path. There is no main-thread fallback.- It is what current browsers ship. Chrome 110+, Edge, Safari, and Firefox 133+ all expose the standard Worker transform. None still expose the main-thread stream. One target API keeps the capture path identical everywhere.
- It is the seam for frame-level end-to-end encryption. Because every frame passes through one Worker, the SDK can later encrypt payloads there. Then even a relay, or a self-hosted SFU you migrate away from, sees ciphertext only.
The hard gate: capture refuses when the transform is absent
The SDK treats the encoded-frame rule as non-negotiable. IfRTCRtpScriptTransform is not a function on globalThis, the SDK refuses to
capture. It does not fall back to an insecure or main-thread path. A helper
reports support:
1
At capture time
captureMicrophone() checks support before it calls getUserMedia, so
it never acquires hardware it cannot use. If the transform is unsupported,
it throws immediately.2
At connect time
When you connect a media session with
requireEncodedTransform: true, and
the runtime lacks the transform, the client stops before it opens the
transport. You never establish a session on which you cannot legally
publish audio.How a frame is tapped and forwarded
After support is confirmed, the capture graph is a short loopback. The steps below trace one microphone frame from silicon to the QUIC wire.1
Run the browser audio pipeline in a loopback
The SDK wires two
RTCPeerConnections to each other locally
(pc1 → pc2). It adds your microphone track to pc1 as a sender. pc2
is only a sink; it forces the encoder to run. The SDK completes the
loopback SDP offer/answer locally, which starts the Opus encoder. The SDK
never uses WebRTC’s ICE/DTLS/SRTP transport to leave the machine.2
Attach the transform to the sender
The SDK creates a Worker and sets it as the sender’s From here, every encoded Opus frame from the sender goes into the Worker.
The frames are not packetized for WebRTC.
transform:3
Read frames off the transform inside the Worker
The Worker handles the The
rtctransform event. Each transformer exposes a
readable (incoming encoded frames) and a writable (the onward pipeline).
The Worker loops: read a frame, copy its payload, post it to the main thread,
then re-enqueue the frame to writable so the sender pipeline stays
unblocked.timestamp on each frame is the RTP timestamp on Opus’s 48 kHz clock.4
Forward each frame to the MoQT publication
Back on the main thread, the Worker’s message handler converts the 48 kHz
RTP timestamp to microseconds and writes the raw encoded payload to the
publication: one MoQT object per Opus frame, carried over WebTransport:
What crosses the wire
The transform hands off encoded Opus payloads, not PCM and not RTP packets. On the wire, the Opus staysopus/48000/2 with a 48 kHz RTP clock, even though
the encoder’s internal rate is driven narrowband for voice. The receiver side
reverses this: an Opus decoder (WebCodecs) feeds an audio worklet ring buffer.
See the OpusPlayer covered in
Browser Audio Capture.
Teardown
The handle returned by capture tears down the graph deterministically: it terminates the Worker, stops the microphone track (unless you supplied your own), and closes both loopback peer connections. It does not close the MoQT publication. You manage that lifecycle on the transport.Lifecycle at a glance
1
Gate
Check
encodedTransformSupported(). Refuse otherwise.2
Capture
getUserMedia runs with AEC/AGC/NS enabled.3
Encode
The loopback
RTCPeerConnection runs the Opus encoder.4
Tap
The Worker
RTCRtpScriptTransform reads encoded frames and re-enqueues them.5
Forward
The main thread writes each frame to the MoQT publication over QUIC.
Related
One-Line WebRTC Diversion
The developer-facing wrapper that hides this whole graph behind one call.
Browser Audio Capture
captureMicrophone, the OpusPlayer, and end-to-end capture wiring.Browser Compatibility
Which engines ship the transform, and where capture is refused.
WebTransport
The browser-native QUIC plane the tapped frames ride on.

