# `XMediaLib.Rtp.HeaderExtension`
[🔗](https://github.com/Lazarus404/xmedialib/blob/main/lib/rtp/header_extension.ex#L26)

Parses RTP header extension elements inside an RFC 3550 extension block.

## What problem this solves

WebRTC packs many RTP header-extension elements (audio level, TWCC,
Dependency Descriptor, RID / `rtp-stream-id`, etc.) into the RFC 3550
extension payload. SFU code needs to decode, rewrite, and re-encode those
elements without corrupting neighbors.

An RTP packet can carry extra metadata after the fixed 12-byte header (for
example, per-participant audio levels in a conference mixer). RFC 3550
defines an optional extension header; RFC 5285 and its successor RFC 8285
define how individual extension elements are packed into the extension
payload. WebRTC commonly uses the one-byte form (profile `0xBEDE`).

This module decodes one-byte (`0xBEDE`) and two-byte (`0x1000`) element
layouts, encodes elements back into a padded payload, and exposes helpers
such as RFC 6464 audio level. Two-byte form is required when an element
exceeds 16 octets (one-byte max).

Typical flow: decode the RTP packet with `XMediaLib.Rtp.decode/1`, read
`%XMediaLib.Rtp.Extension{}` from `:extension`, then call
`decode_elements/2` with the profile `type`.

## Profiles

- `:one_byte` / `0xBEDE` -- RFC 8285 pt.4.2
- `:two_byte` / `0x1000` -- RFC 8285 pt.4.3

## RFCs

* [RFC 3550](https://www.rfc-editor.org/rfc/rfc3550) - RTP header extension
  container (profile-specific extension)
* [RFC 5285](https://www.rfc-editor.org/rfc/rfc5285) - one-byte and two-byte
  header extensions (obsoleted by RFC 8285)
* [RFC 8285](https://www.rfc-editor.org/rfc/rfc8285) - current header
  extension specification
* [RFC 6464](https://www.rfc-editor.org/rfc/rfc6464) - Client-to-Mixer Audio
  Level Indication

# `audio_level`

```elixir
@spec audio_level([{pos_integer(), binary()}], pos_integer()) ::
  {:ok, %{voice?: boolean(), level: 0..127}} | :error
```

Reads RFC 6464 audio level from a decoded element list for local `id`.

The first octet of the element data is `V` (voice activity, 1 bit) and
`level` (7 bits, 0 is loudest, 127 is silence).

Returns `{:ok, %{voice?: boolean(), level: 0..127}}` or `:error` when the id
is missing or the data is too short.

## Examples

    iex> elements = [{1, <<0x8A>>}, {3, <<0x01>>}]
    iex> XMediaLib.Rtp.HeaderExtension.audio_level(elements, 1)
    {:ok, %{voice?: true, level: 10}}

    iex> XMediaLib.Rtp.HeaderExtension.audio_level([{2, <<0x8A>>}], 1)
    :error

# `decode_elements`

```elixir
@spec decode_elements(non_neg_integer(), binary()) :: [{pos_integer(), binary()}]
```

Decodes extension elements for profile `type` (`0xBEDE` or `0x1000`).

Unknown profiles return `[]`.

# `decode_one_byte_elements`

```elixir
@spec decode_one_byte_elements(binary()) :: [{pos_integer(), binary()}]
```

Decodes a one-byte (RFC 8285 pt.4.2) extension payload into `{id, data}` pairs.

Skips zero padding octets. Stops on truncated data or reserved local id 15.

## Examples

    iex> payload = <<1::4, 0::4, 0x8A, 0, 0>>
    iex> XMediaLib.Rtp.HeaderExtension.decode_one_byte_elements(payload)
    [{1, <<0x8A>>}]

    iex> payload = <<1::4, 0::4, 0x05, 0, 2::4, 1::4, 0xAA, 0xBB>>
    iex> XMediaLib.Rtp.HeaderExtension.decode_one_byte_elements(payload)
    [{1, <<0x05>>}, {2, <<0xAA, 0xBB>>}]

# `decode_two_byte_elements`

```elixir
@spec decode_two_byte_elements(binary()) :: [{pos_integer(), binary()}]
```

Decodes a two-byte (RFC 8285 pt.4.3) extension payload into `{id, data}` pairs.

Skips zero padding. Stops on truncated data. Id 0 is padding (skipped).

# `encode_elements`

```elixir
@spec encode_elements(non_neg_integer(), [{pos_integer(), binary()}]) :: binary()
```

Encodes elements for profile `type`.

For `0xBEDE`, elements with data longer than 16 octets are dropped from the
one-byte encode (callers should upgrade profile via `profile_for/1`).

# `encode_one_byte_elements`

```elixir
@spec encode_one_byte_elements([{pos_integer(), binary()}]) :: binary()
```

Encodes `{id, data}` pairs as a one-byte (RFC 8285) extension payload.

Pads to a multiple of 4 octets. Ids must be 1..14; empty list yields
four zero padding octets.

# `encode_two_byte_elements`

```elixir
@spec encode_two_byte_elements([{pos_integer(), binary()}]) :: binary()
```

Encodes `{id, data}` pairs as a two-byte (RFC 8285) extension payload.

Pads to a multiple of 4 octets. Ids must be 1..255; data length 0..255.

# `one_byte_profile`

```elixir
@spec one_byte_profile() :: 48862
```

One-byte profile id (`0xBEDE`).

# `profile_for`

```elixir
@spec profile_for([{pos_integer(), binary()}]) :: 48862 | 4096
```

Chooses `0xBEDE` when every element fits one-byte rules; otherwise `0x1000`.

# `rtp_stream_id`

```elixir
@spec rtp_stream_id([{pos_integer(), binary()}], pos_integer()) ::
  {:ok, binary()} | :error
```

Reads the RID string from a decoded element list for local `id`.

WebRTC packs the Restriction Identifier as UTF-8 / ASCII in the
`sdes:rtp-stream-id` extension payload.

Returns `{:ok, rid}` or `:error` when missing or empty.

## Examples

    iex> XMediaLib.Rtp.HeaderExtension.rtp_stream_id([{4, "q"}], 4)
    {:ok, "q"}

    iex> XMediaLib.Rtp.HeaderExtension.rtp_stream_id([{4, "q"}], 5)
    :error

# `rtp_stream_id_uri`

```elixir
@spec rtp_stream_id_uri() :: String.t()
```

SDP extmap URI for the RTP Stream Identifier (RID) header extension.

# `two_byte_profile`

```elixir
@spec two_byte_profile() :: 4096
```

Two-byte profile id (`0x1000`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
