RTP fixed header encoding and decoding (RFC 3550).

Every RTP packet opens with a 12-byte fixed header, an optional contributing-source (CSRC) list sized by the header's csrcCount field, and the codec payload:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Timestamp                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Synchronization Source (SSRC) Identifier           |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            Contributing Source (CSRC) Identifiers            |
|                             ....                              |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|                        Payload (variable)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

csrcCount (the low 4 bits of the first octet) gives the number of 32-bit CSRC identifiers that follow the SSRC, so the CSRC list is always csrcCount words long. Bit-packed fields (version/padding/extension/CC, marker/payload-type) are exposed as nested objects via bitStruct, keeping the on-wire layout faithful while preserving named-field access.

This package covers the fixed header and CSRC list only. When extension is set, RFC 3550 §5.3.1 defines a header-extension block between the CSRC list and the payload; this coder does not parse it and instead treats everything after the CSRC list as opaque payload bytes. Deeper parsing, extension headers, and payload-format-specific decoding (RTCP, SRTP, codec payloads) are left to future packages — v0.0.1 is header-level only.

Examples

Round-trip a packet with no CSRC entries

import { assertEquals } from "@std/assert";
import { rtpPacket, RTP_HEADER_MIN_SIZE, RTP_VERSION } from "@binstruct/rtp";

const coder = rtpPacket();
const payload = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
const packet = {
  versionFlags: { version: RTP_VERSION, padding: 0, extension: 0, csrcCount: 0 },
  markerPayloadType: { marker: 0, payloadType: 0 },
  sequenceNumber: 1,
  timestamp: 0,
  ssrc: 0x11223344,
  csrc: [],
  payload,
};

const buffer = new Uint8Array(RTP_HEADER_MIN_SIZE + payload.length);
const written = coder.encode(packet, buffer);
const [decoded, read] = coder.decode(buffer.subarray(0, written));

assertEquals(written, RTP_HEADER_MIN_SIZE + payload.length);
assertEquals(read, written);
assertEquals(decoded.ssrc, packet.ssrc);
assertEquals(decoded.payload, payload);