Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

HEVC NAL Unit Headers and RTP Fragmentation Units in RTP Streaming

Tech 1

HEVC NAL unit header (2 bytes)

HEVC encodes each NAL unit with a 16‑bit header laid out as:

  • forbidden_zero_bit: 1 bit (must be 0)
  • nal_unit_type: 6 bits
  • nuh_layer_id: 6 bits
  • nuh_temporal_id_plus1: 3 bits

Bit packing across the two bytes (b7 is MSB):

  • byte0: b7=fzb, b6..b1=type, b0=layer_id[5]
  • byte1: b7..b3=layer_id[4:0], b2..b0=TID+1

Common single-layer, single-temporal-stream values: fzb=0, layer_id=0, nuh_temporal_id_plus1=1.

C helpers too extract fields:

static inline uint8_t hevc_forbidden_bit(const uint8_t *n)
{
    return (n[0] >> 7) & 0x01;
}

static inline uint8_t hevc_nal_type(const uint8_t *n)
{
    return (n[0] >> 1) & 0x3F; // 0..63
}

static inline uint8_t hevc_layer_id(const uint8_t *n)
{
    return ((n[0] & 0x01) << 5) | ((n[1] & 0xF8) >> 3); // 6 bits
}

static inline uint8_t hevc_tid_plus1(const uint8_t *n)
{
    return n[1] & 0x07; // 1..7
}

Selected HEVC NAL unit types

Only a subset is most relevant to RTP packetization:

enum HevcNalType {
    HEVC_NAL_TRAIL_N  = 0,
    HEVC_NAL_TRAIL_R  = 1,
    HEVC_NAL_TSA_N    = 2,
    HEVC_NAL_TSA_R    = 3,
    HEVC_NAL_STSA_N   = 4,
    HEVC_NAL_STSA_R   = 5,
    HEVC_NAL_RADL_N   = 6,
    HEVC_NAL_RADL_R   = 7,
    HEVC_NAL_RASL_N   = 8,
    HEVC_NAL_RASL_R   = 9,

    // IRAP picture types
    HEVC_NAL_BLA_W_LP = 16,
    HEVC_NAL_BLA_W_DLP= 17,
    HEVC_NAL_BLA_N_LP = 18,
    HEVC_NAL_IDR_W_DLP= 19,
    HEVC_NAL_IDR_N_LP = 20,
    HEVC_NAL_CRA      = 21,

    // parameter and SEI sets
    HEVC_NAL_VPS      = 32,
    HEVC_NAL_SPS      = 33,
    HEVC_NAL_PPS      = 34,
    HEVC_NAL_AUD      = 35,
    HEVC_NAL_EOS      = 36,
    HEVC_NAL_EOB      = 37,
    HEVC_NAL_FD       = 38,
    HEVC_NAL_SEI_PREFIX = 39,
    HEVC_NAL_SEI_SUFFIX = 40,

    // RTP aggregation/fragmentation
    HEVC_NAL_AP       = 48, // Aggregation Packet
    HEVC_NAL_FU       = 49  // Fragmentation Unit
};

RTP packetizasion: single NAL vs. FU (fragmentation)

For RTP per RFC 7798:

  • If the NAL unit size fits the payload budget, transmit it as a Single NAL Unit packet (payload starts with the NAL header).
  • If it exceeds the payload budget, split it into Fragmentation Units (FU):
    • Payload Header: 2 bytes, same layout as the HEVC NAL header, but nal_unit_type=49 (FU). F, LayerId, and TID should be copied from the original NAL header for multilayer/temporla streams. In simple single-layer cases they are typically F=0, LayerId=0, TID+1=1.
    • FU Header: 1 byte with fields S|E|FuType (S=start bit, E=end bit, FuType=original NAL unit type).
    • FU payload: the original NAL unit payload excluding its 2‑byte HEVC NAL header.

Bit layout of the FU header:

  • bit7: S (1 for the first fragment)
  • bit6: E (1 for the last fragment)
  • bits5..0: FuType (oriignal nal_unit_type)

Building the FU payload header

Given original NAL header fields F, Lid, TIDp1 and the fixed FU type (49), the two payload header bytes for a FU are constructed as:

static inline void hevc_make_fu_payload_header(uint8_t fzb,
                                              uint8_t layer_id,
                                              uint8_t tid_plus1,
                                              uint8_t out[2])
{
    const uint8_t fu_type = 49; // HEVC_NAL_FU
    out[0] = (fzb & 0x1) << 7;
    out[0] |= (fu_type & 0x3F) << 1;
    out[0] |= (layer_id >> 5) & 0x01; // layer_id[5]

    out[1] = ((layer_id & 0x1F) << 3) | (tid_plus1 & 0x07);
}

FU packetization implementation (C)

The following function fragments an oversized HEVC NAL unit into RTP FU packets. It copies F/LayerId/TID from the original NAL header (recommended for generality). The RTP sender callback must attach the RTP header and set the marker bit on the last packet of the access unit as appropriate.

typedef struct {
    int mtu;                  // Maximum network MTU including RTP header
    int max_payload;          // Budget for RTP payload bytes (no RTP header)
    void (*rtp_send)(const uint8_t *payload, int size, int marker);
} RtpHevcOut;

static void rtp_send_hevc_nal(RtpHevcOut *s,
                              const uint8_t *nalu,
                              int nalu_size,
                              int is_last_in_au)
{
    if (nalu_size <= 0) return;

    const int nal_hdr_bytes = 2;         // HEVC NAL header length
    const int fu_overhead   = 2 + 1;     // payload header (2) + FU header (1)
    const int room          = s->max_payload - fu_overhead;

    uint8_t fzb     = hevc_forbidden_bit(nalu);
    uint8_t ntype   = hevc_nal_type(nalu);
    uint8_t lid     = hevc_layer_id(nalu);
    uint8_t tidp1   = hevc_tid_plus1(nalu);

    // If it fits, send as a single NAL
    if (nalu_size <= s->max_payload) {
        s->rtp_send(nalu, nalu_size, is_last_in_au);
        return;
    }

    // Otherwise, fragment using FU (Type=49)
    uint8_t hdr[2];
    hevc_make_fu_payload_header(fzb, lid, tidp1, hdr);

    uint8_t fu_hdr = 0;
    fu_hdr |= (1u << 7);           // S=1 for the first fragment
    fu_hdr |= (ntype & 0x3F);      // FuType = original nal_unit_type

    const uint8_t *payload = nalu + nal_hdr_bytes;
    int payload_left = nalu_size - nal_hdr_bytes;

    // Emit middle fragments (S cleared, E=0)
    while (payload_left > room) {
        // build packet: [payload header (2)] [FU header (1)] [payload chunk]
        uint8_t pkt[3 + 1500]; // temporary buffer; ensure > fu_overhead + room as needed
        int copy = room;
        pkt[0] = hdr[0];
        pkt[1] = hdr[1];
        pkt[2] = fu_hdr; // S=1 for first, cleared below for the rest
        memcpy(pkt + fu_overhead, payload, copy);
        s->rtp_send(pkt, fu_overhead + copy, 0);

        // Advance
        payload      += copy;
        payload_left -= copy;
        fu_hdr       &= ~(1u << 7); // clear S after first packet
    }

    // Last fragment: set E=1
    fu_hdr |= (1u << 6);
    {
        uint8_t pkt[3 + 1500];
        pkt[0] = hdr[0];
        pkt[1] = hdr[1];
        pkt[2] = fu_hdr;
        memcpy(pkt + fu_overhead, payload, payload_left);
        s->rtp_send(pkt, fu_overhead + payload_left, is_last_in_au);
    }
}

Notes:

  • room = max_payload − 3 accounts for the 2‑byte HEVC payload header and 1‑byte FU header.
  • The FU payload omits the original 2‑byte HEVC NAL header.
  • For simple single-layer streams, payload header fields can be set to F=0, LayerId=0, TID+1=1 if you don't need to preserve the original values.

Minimal single‑NAL fast path

When the NAL unit fits the payload budget, transmit it unchanged as the RTP payload:

static inline void rtp_send_hevc_single(RtpHevcOut *s,
                                        const uint8_t *nalu,
                                        int nalu_size,
                                        int is_last_in_au)
{
    s->rtp_send(nalu, nalu_size, is_last_in_au);
}

Putting it together

A typical sender chooses per NAL unit between single‑NAL and FU modes:

static void rtp_send_hevc_access_unit(RtpHevcOut *s,
                                      const uint8_t *data,
                                      int size)
{
    // This example assumes data points at exactly one NALU. If you have an AU
    // with multiple NALUs, iterate and set is_last_in_au only for the last.
    const int is_last = 1;
    if (size <= s->max_payload) {
        rtp_send_hevc_single(s, data, size, is_last);
    } else {
        rtp_send_hevc_nal(s, data, size, is_last);
    }
}

Reference

  • RTP Payload Format for High Efficiency Video Coding (HEVC) — RFC 7798
  • FFmpeg rtpenc_hevc.c (implementation of HEVC RTP packetization)
Tags: HEVCH.265RTP

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.