/ parabun

bun:rtp

RFC 3550 packet pack / parse and a jitter buffer. Wire transport for the codec stack.

ts
import rtp from "bun:rtp";

A small RTP toolkit — pack a payload into an RFC 3550 packet, parse one off the wire, and reorder by sequence number with a configurable depth. Built to sit under bun:audio's Opus encoder for a WebRTC-style send/receive path.

pack(opts)

Returns a Uint8Array containing the RTP header + payload.

ts
const packet = rtp.pack({
  payloadType: 111,         // 7 bits
  sequence: 1234,            // 16 bits
  timestamp: 48000,          // 32 bits
  ssrc: 0xDEADBEEF,          // 32 bits
  payload: opusFrame,        // Uint8Array
  marker: false,             // bool, default false
});

CSRCs and extensions aren't supported — the header is fixed at the 12-byte minimum. Open an issue if you need them.

parse(bytes)

Parses a single RTP packet. Returns { payloadType, sequence, timestamp, ssrc, payload, marker }. Throws if the version field isn't 2 or the length is too short.

ts
const { payloadType, sequence, timestamp, payload } = rtp.parse(packet);

JitterBuffer

Reorders incoming packets by sequence number. Useful when the network delivers out-of-order or duplicates.

ts
const buf = new rtp.JitterBuffer({ depth: 8 });

// ingest as packets arrive
buf.push(packet);
buf.push(packet2);

// drain in order
for (const ordered of buf.drain()) {
  decoder.decode(ordered.payload);
}
OptionDefaultDescription
depth8Max reorder window. Packets older than tail - depth are dropped.
wrapAwaretrueHandles 16-bit sequence-number wrap.

drain() yields packets in sequence order until it would have to wait for a missing one. Subsequent push calls + drain cycles continue from where it stopped.

A full audio pipeline

Combined with bun:audio:

ts
import audio from "bun:audio";
import rtp from "bun:rtp";

const enc = new audio.OpusEncoder({ sampleRate: 48000, channels: 1, application: "voip" });
const den = new audio.Denoiser();
const agc = new audio.Gain({ targetLevel: 0.1 });

let sequence = 0, timestamp = 0;
const ssrc = (Math.random() * 0xFFFFFFFF) | 0;

for (const i16Frame of micFrames) {
  const f32 = audio.i16ToF32(i16Frame);
  den.process(f32);
  agc.process(f32);
  const opus = enc.encode(f32);

  const packet = rtp.pack({
    payloadType: 111,
    sequence: sequence++,
    timestamp,
    ssrc,
    payload: opus,
  });
  send(packet);

  timestamp += f32.length;       // advance by sample count
}

Limits