RTSP推流:RTP包组装逻辑详解

本文档结合FFmpeg源代码,详细解释RTSP推流时如何将媒体数据组装成RTP包的完整流程。

一、整体流程概览

复制代码
AVPacket (原始编码数据)
    │
    ▼

rtsp_write_packet() [libavformat/rtspenc.c:182]
    │
    ├─→ 根据stream_index找到对应的RTSPStream
    ├─→ 获取RTP Muxer Context (rtpctx)
    │
    ▼
ff_write_chained() [libavformat/mux.c:1337]
    │
    ├─→ 时间戳转换 (从源流时间基转换到RTP流时间基)
    ├─→ 调用RTP Muxer的write_packet
    │
    ▼
rtp_write_packet() [libavformat/rtpenc.c:541]
    │
    ├─→ RTCP发送检查 (每5秒或达到阈值时发送SR)
    ├─→ 时间戳计算: cur_timestamp = base_timestamp + pkt->pts
    ├─→ 根据编解码器类型调用特定封装函数
    │
    ▼
编解码器特定封装函数
    ├─→ H.264/HEVC: ff_rtp_send_h264_hevc()
    ├─→ AAC: ff_rtp_send_aac()
    ├─→ 其他编解码器...
    │
    ▼
ff_rtp_send_data() [libavformat/rtpenc.c:356]
    │
    ├─→ 构建RTP固定头 (12字节)
    ├─→ 写入Payload数据
    ├─→ 更新序列号和统计信息
    │
    ▼
传输层
    ├─→ UDP模式: 直接写入UDP socket
    └─→ TCP模式: 写入动态缓冲区,然后添加Interleaved头发送

二、核心函数详解

2.1 rtsp_write_packet() - RTSP推流入口

位置 : libavformat/rtspenc.c:182

c 复制代码
static int rtsp_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    RTSPState *rt = s->priv_data;
    RTSPStream *rtsp_st;
    AVFormatContext *rtpctx;
    int ret;

    // 1. 检查并处理RTSP控制消息 (poll非阻塞检查)
    // ... (省略poll逻辑)

    // 2. 根据stream_index找到对应的RTSPStream
    if (pkt->stream_index < 0 || pkt->stream_index >= rt->nb_rtsp_streams)
        return AVERROR_INVALIDDATA;
    rtsp_st = rt->rtsp_streams[pkt->stream_index];
    rtpctx = rtsp_st->transport_priv;  // 获取RTP Muxer Context

    // 3. 调用链式写入,将数据传递给RTP Muxer
    ret = ff_write_chained(rtpctx, 0, pkt, s, 0);
    
    // 4. TCP模式需要额外处理Interleaved封装
    if (!ret && rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP)
        ret = ff_rtsp_tcp_write_packet(s, rtsp_st);
    return ret;
}

关键点:

  • 每个AVStream对应一个RTSPStream,每个RTSPStream有自己的RTP Muxer Context
  • ff_write_chained() 负责将数据传递给RTP Muxer进行封装
  • TCP模式下需要额外的Interleaved封装处理

2.2 ff_write_chained() - 链式写入

位置 : libavformat/mux.c:1337

c 复制代码
int ff_write_chained(AVFormatContext *dst, int dst_stream, AVPacket *pkt,
                     AVFormatContext *src, int interleave)
{
    int64_t pts = pkt->pts, dts = pkt->dts, duration = pkt->duration;
    int stream_index = pkt->stream_index;
    AVRational time_base = pkt->time_base;
    int ret;

    // 1. 修改packet的stream_index为目标流的索引
    pkt->stream_index = dst_stream;

    // 2. 时间戳转换:从源流时间基转换到目标流时间基
    av_packet_rescale_ts(pkt,
                         src->streams[stream_index]->time_base,
                         dst->streams[dst_stream]->time_base);

    // 3. 调用目标muxer的write_packet
    if (!interleave) {
        ret = av_write_frame(dst, pkt);
    } else
        ret = av_interleaved_write_frame(dst, pkt);

    // 4. 恢复packet的原始字段
    pkt->pts          = pts;
    pkt->dts          = dts;
    pkt->duration     = duration;
    pkt->stream_index = stream_index;
    pkt->time_base    = time_base;

    return ret;
}

关键点:

  • 时间戳转换:将源流的时间基转换为RTP流的时间基
  • RTP流的时间基通常是:音频为采样率,视频为90000Hz

2.3 rtp_write_packet() - RTP封装主函数

位置 : libavformat/rtpenc.c:541

c 复制代码
static int rtp_write_packet(AVFormatContext *s1, AVPacket *pkt)
{
    RTPMuxContext *s = s1->priv_data;
    AVStream *st = s1->streams[0];
    int rtcp_bytes;
    int size = pkt->size;

    // 1. RTCP发送检查
    // 计算自上次SR以来发送的字节数
    rtcp_bytes = ((s->octet_count - s->last_octet_count) * RTCP_TX_RATIO_NUM) /
                  RTCP_TX_RATIO_DEN;
    
    // 满足以下条件之一时发送RTCP SR:
    // - 第一个包
    // - 发送的字节数 >= RTCP_SR_SIZE (28字节)
    // - 距离上次SR超过5秒
    if ((s->first_packet || 
         ((rtcp_bytes >= RTCP_SR_SIZE) &&
          (ff_ntp_time() - s->last_rtcp_ntp_time > 5000000))) &&
        !(s->flags & FF_RTP_FLAG_SKIP_RTCP)) {
        rtcp_send_sr(s1, ff_ntp_time(), 0);
        s->last_octet_count = s->octet_count;
        s->first_packet = 0;
    }

    // 2. 计算RTP时间戳
    // base_timestamp是随机初始值,用于避免时间戳冲突
    s->cur_timestamp = s->base_timestamp + pkt->pts;

    // 3. 根据编解码器类型调用特定的封装函数
    switch(st->codecpar->codec_id) {
    case AV_CODEC_ID_H264:
        ff_rtp_send_h264_hevc(s1, pkt->data, size);
        break;
    case AV_CODEC_ID_AAC:
        if (s->flags & FF_RTP_FLAG_MP4A_LATM)
            ff_rtp_send_latm(s1, pkt->data, size);
        else
            ff_rtp_send_aac(s1, pkt->data, size);
        break;
    // ... 其他编解码器
    default:
        rtp_send_raw(s1, pkt->data, size);
        break;
    }
    return 0;
}

关键点:

  • RTCP SR (Sender Report) 定期发送,用于同步和统计
  • RTP时间戳 = base_timestamp + packet的PTS
  • 不同编解码器有不同的RTP封装方式

2.4 ff_rtp_send_data() - RTP固定头构建

位置 : libavformat/rtpenc.c:356

c 复制代码
void ff_rtp_send_data(AVFormatContext *s1, const uint8_t *buf1, int len, int m)
{
    RTPMuxContext *s = s1->priv_data;

    /* 构建RTP固定头 (12字节) */
    
    // 字节0: V(2 bits) + P(1 bit) + X(1 bit) + CC(4 bits)
    // V=2 (RTP版本), P=0, X=0, CC=0
    avio_w8(s1->pb, RTP_VERSION << 6);  // 0x80 = 10000000
    
    // 字节1: M(1 bit) + PT(7 bits)
    // M=marker bit (m参数), PT=payload type
    avio_w8(s1->pb, (s->payload_type & 0x7f) | ((m & 0x01) << 7));
    
    // 字节2-3: Sequence Number (16 bits)
    avio_wb16(s1->pb, s->seq);
    
    // 字节4-7: Timestamp (32 bits)
    avio_wb32(s1->pb, s->timestamp);
    
    // 字节8-11: SSRC (32 bits)
    avio_wb32(s1->pb, s->ssrc);

    // 写入Payload数据
    avio_write(s1->pb, buf1, len);
    avio_flush(s1->pb);

    // 更新统计信息
    s->seq = (s->seq + 1) & 0xffff;  // 序列号递增,16位循环
    s->octet_count += len;            // 累计发送字节数
    s->packet_count++;                // 累计发送包数
}

RTP固定头结构 (12字节):

复制代码
 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              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段说明:

  • V (2 bits): RTP版本,固定为2
  • P (1 bit): Padding标志,通常为0
  • X (1 bit): Extension标志,通常为0
  • CC (4 bits): CSRC计数,通常为0
  • M (1 bit): Marker位,用于标记关键帧或帧边界
  • PT (7 bits): Payload Type,标识编码格式 (如H.264=96, AAC=97)
  • Sequence Number (16 bits): 序列号,每包递增,用于检测丢包
  • Timestamp (32 bits): 时间戳,基于采样时钟
  • SSRC (32 bits): 同步源标识符,随机生成,用于区分不同流

三、编解码器特定封装详解

3.1 H.264/HEVC封装 - FU-A分片模式

位置 : libavformat/rtpenc_h264_hevc.c

3.1.1 小NAL单元处理 (Single NAL Unit Mode)

当NAL单元大小 <= max_payload_size时,可以直接发送:

c 复制代码
void ff_rtp_send_h264_hevc(AVFormatContext *s1, const uint8_t *buf1, int size)
{
    // 1. 查找NAL单元起始码 (0x00000001 或 0x000001)
    r = ff_nal_find_startcode(buf1, end);
    
    while (r < end) {
        // 2. 找到下一个NAL单元起始码
        r1 = ff_nal_find_startcode(r + 1, end);
        
        // 3. 提取NAL单元
        nal_size = r1 - r - 1;
        nal_buf = r + 1;  // 跳过起始码
        
        // 4. 调用nal_send处理
        nal_send(s1, nal_buf, nal_size, last);
    }
}
3.1.2 NAL单元封装逻辑
c 复制代码
static void nal_send(AVFormatContext *s1, const uint8_t *buf, int size, int last)
{
    RTPMuxContext *s = s1->priv_data;
    
    if (size <= s->max_payload_size) {
        // 情况1: 可以聚合多个小NAL单元 (STAP-A模式)
        if (buffered_size + 2 + size <= s->max_payload_size) {
            if (buffered_size == 0) {
                *s->buf_ptr++ = 24;  // STAP-A类型
            }
            AV_WB16(s->buf_ptr, size);  // NAL单元长度 (2字节)
            s->buf_ptr += 2;
            memcpy(s->buf_ptr, buf, size);  // NAL单元数据
            s->buf_ptr += size;
            s->buffered_nals++;
        } else {
            // 情况2: 单个NAL单元直接发送
            flush_buffered(s1, 0);
            ff_rtp_send_data(s1, buf, size, last);
        }
    } else {
        // 情况3: 大NAL单元需要分片 (FU-A模式)
        flush_buffered(s1, 0);
        
        // 构建FU-A头
        uint8_t type = buf[0] & 0x1F;      // NAL类型
        uint8_t nri = buf[0] & 0x60;       // NRI (重要性)
        
        s->buf[0] = 28;        // FU Indicator: Type=28 (FU-A)
        s->buf[0] |= nri;      // 保留NRI
        s->buf[1] = type;      // FU Header: NAL类型
        s->buf[1] |= 1 << 7;   // S=1 (Start fragment)
        
        buf  += 1;   // 跳过原始NAL头
        size -= 1;
        
        // 分片发送
        while (size + 2 > s->max_payload_size) {
            memcpy(&s->buf[2], buf, s->max_payload_size - 2);
            ff_rtp_send_data(s1, s->buf, s->max_payload_size, 0);
            buf  += s->max_payload_size - 2;
            size -= s->max_payload_size - 2;
            s->buf[1] &= ~(1 << 7);  // 清除S位
        }
        
        // 最后一个分片
        s->buf[1] |= 1 << 6;  // E=1 (End fragment)
        memcpy(&s->buf[2], buf, size);
        ff_rtp_send_data(s1, s->buf, size + 2, last);
    }
}
3.1.3 H.264 RTP Payload格式

Single NAL Unit Mode:

复制代码
RTP Header (12 bytes)
    │
    ▼
NAL Unit (直接包含NAL单元,去掉起始码)

STAP-A (Single-Time Aggregation Packet):

复制代码
RTP Header (12 bytes)
    │
    ▼
STAP-A Header (1 byte): 0x18 (24)
    │
    ▼
NAL Unit 1 Length (2 bytes)
    │
    ▼
NAL Unit 1 Data
    │
    ▼
NAL Unit 2 Length (2 bytes)
    │
    ▼
NAL Unit 2 Data
    ...

FU-A (Fragmentation Unit):

复制代码
RTP Header (12 bytes)
    │
    ▼
FU Indicator (1 byte)
    ├─→ F (1 bit) = 0
    ├─→ NRI (2 bits) = 从原NAL头复制
    └─→ Type (5 bits) = 28 (FU-A)
    │
    ▼
FU Header (1 byte)
    ├─→ S (1 bit) = 1 (Start) / 0 (Continue)
    ├─→ E (1 bit) = 0 (Continue) / 1 (End)
    ├─→ R (1 bit) = 0 (Reserved)
    └─→ Type (5 bits) = 原NAL类型
    │
    ▼
FU Payload (NAL单元数据,去掉NAL头)

3.2 AAC封装 - MPEG4-GENERIC格式

位置 : libavformat/rtpenc_aac.c

c 复制代码
void ff_rtp_send_aac(AVFormatContext *s1, const uint8_t *buff, int size)
{
    RTPMuxContext *s = s1->priv_data;
    AVStream *st = s1->streams[0];
    const int max_au_headers_size = 2 + 2 * s->max_frames_per_packet;
    int len, max_packet_size = s->max_payload_size - max_au_headers_size;
    uint8_t *p;

    // 1. 跳过ADTS头 (如果存在)
    if ((s1->streams[0]->codecpar->extradata_size) == 0) {
        size -= 7;  // ADTS头7字节
        buff += 7;
    }

    // 2. 检查是否需要发送当前缓冲的包
    len = (s->buf_ptr - s->buf);
    if (s->num_frames &&
        (s->num_frames == s->max_frames_per_packet ||
         (len + size) > s->max_payload_size ||
         时间戳差异超过max_delay)) {
        // 发送缓冲的包
        int au_size = s->num_frames * 2;
        p = s->buf + max_au_headers_size - au_size - 2;
        if (p != s->buf) {
            memmove(p + 2, s->buf + 2, au_size);
        }
        AV_WB16(p, au_size * 8);  // AU Header Size (单位: bits)
        ff_rtp_send_data(s1, p, s->buf_ptr - p, 1);
        s->num_frames = 0;
    }

    // 3. 初始化缓冲区 (如果是新包)
    if (s->num_frames == 0) {
        s->buf_ptr = s->buf + max_au_headers_size;
        s->timestamp = s->cur_timestamp;
    }

    // 4. 添加AU到缓冲区
    if (size <= max_packet_size) {
        p = s->buf + s->num_frames++ * 2 + 2;
        AV_WB16(p, size * 8);  // AU Size (单位: bits)
        memcpy(s->buf_ptr, buff, size);
        s->buf_ptr += size;
    } else {
        // 5. 大AU需要分片发送
        int au_size = size;
        max_packet_size = s->max_payload_size - 4;
        p = s->buf;
        AV_WB16(p, 2 * 8);  // AU Header Size = 2 bytes
        while (size > 0) {
            len = FFMIN(size, max_packet_size);
            AV_WB16(&p[2], au_size * 8);  // AU Size
            memcpy(p + 4, buff, len);
            ff_rtp_send_data(s1, p, len + 4, len == size);
            size -= len;
            buff += len;
        }
    }
}
3.2.1 AAC RTP Payload格式 (MPEG4-GENERIC)
复制代码
RTP Header (12 bytes)
    │
    ▼
AU Header Section
    ├─→ AU Header Length (2 bits) = 0 (表示16位AU头)
    └─→ AU Header (16 bits per AU)
        ├─→ AU Size (13 bits): ADTS帧大小 (单位: bytes)
        └─→ AU Index (3 bits): 索引 (通常为0)
    │
    ▼
AU Payload Section
    └─→ ADTS Frame 1 | ADTS Frame 2 | ...

多AU聚合示例:

复制代码
RTP Header
    │
    ▼
AU Header Section (2 + N*2 bytes)
    ├─→ AU Header Length = 16 (2 bits, 单位: bits)
    ├─→ AU Header 1 (16 bits)
    │   ├─→ Size 1 (13 bits)
    │   └─→ Index 1 (3 bits)
    ├─→ AU Header 2 (16 bits)
    │   ├─→ Size 2 (13 bits)
    │   └─→ Index 2 (3 bits)
    ...
    │
    ▼
AU Payload Section
    ├─→ ADTS Frame 1 (Size 1 bytes)
    ├─→ ADTS Frame 2 (Size 2 bytes)
    ...

四、传输层处理

4.1 UDP模式

UDP模式下,RTP包直接写入UDP socket:

c 复制代码
// 在rtp_write_header中设置
rtpctx->pb = ffio_fdopen(&rtpctx->pb, udp_handle);
// udp_handle是UDP URLContext

// ff_rtp_send_data直接写入
avio_write(s1->pb, buf1, len);  // 写入UDP socket

4.2 TCP Interleaved模式

TCP模式下,需要添加Interleaved头:

位置 : libavformat/rtspenc.c:143

c 复制代码
int ff_rtsp_tcp_write_packet(AVFormatContext *s, RTSPStream *rtsp_st)
{
    RTSPState *rt = s->priv_data;
    AVFormatContext *rtpctx = rtsp_st->transport_priv;
    uint8_t *buf, *ptr;
    int size;
    uint8_t *interleave_header, *interleaved_packet;

    // 1. 从动态缓冲区获取RTP包数据
    size = avio_close_dyn_buf(rtpctx->pb, &buf);
    rtpctx->pb = NULL;
    ptr = buf;
    
    // 2. 解析并添加Interleaved头
    while (size > 4) {
        uint32_t packet_len = AV_RB32(ptr);  // 读取包长度
        int id;
        
        // 判断是RTP还是RTCP
        if (RTP_PT_IS_RTCP(ptr[5]))  // 检查Payload Type
            id = rtsp_st->interleaved_max;  // RTCP通道
        else
            id = rtsp_st->interleaved_min;  // RTP通道
        
        // 构建Interleaved头 (覆盖原来的长度字段)
        interleaved_packet = interleave_header = ptr;
        ptr += 4;
        size -= 4;
        
        if (packet_len > size || packet_len < 2)
            break;
        
        // Interleaved头格式: '$' + Channel ID + Packet Length
        interleave_header[0] = '$';                    // 标记字节
        interleave_header[1] = id;                    // 通道ID
        AV_WB16(interleave_header + 2, packet_len);    // 包长度
        
        // 3. 发送Interleaved包
        ffurl_write(rt->rtsp_hd_out, interleaved_packet, 4 + packet_len);
        
        ptr += packet_len;
        size -= packet_len;
    }
    
    av_free(buf);
    
    // 4. 重新打开动态缓冲区
    return ffio_open_dyn_packet_buf(&rtpctx->pb, rt->pkt_size);
}
4.2.1 TCP Interleaved格式
复制代码
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| '$' | Channel ID |           Packet Length (16 bits)           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      RTP/RTCP Packet Data                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

说明:

  • '$' (0x24): Interleaved包标记
  • Channel ID :
    • RTP通道: interleaved_min (通常为0)
    • RTCP通道: interleaved_max (通常为1)
  • Packet Length: RTP/RTCP包的长度 (不包括Interleaved头)
  • Packet Data: 完整的RTP或RTCP包

五、关键数据结构

5.1 RTPMuxContext

位置 : libavformat/rtpenc.h:27

c 复制代码
struct RTPMuxContext {
    const AVClass *av_class;
    AVFormatContext *ic;
    AVStream *st;
    
    // RTP头字段
    int payload_type;        // Payload Type (0-127)
    uint32_t ssrc;           // SSRC标识符
    int seq;                 // 序列号
    uint32_t timestamp;      // 当前时间戳
    uint32_t base_timestamp; // 基础时间戳 (随机初始值)
    uint32_t cur_timestamp;  // 当前包的时间戳
    
    // 包大小限制
    int max_payload_size;    // 最大Payload大小 (MTU - IP头 - UDP头 - RTP头)
    
    // 缓冲区
    uint8_t *buf;            // 输出缓冲区
    uint8_t *buf_ptr;        // 缓冲区当前指针
    
    // 统计信息 (用于RTCP)
    unsigned int packet_count;      // 发送包数
    unsigned int octet_count;       // 发送字节数
    unsigned int last_octet_count;  // 上次SR时的字节数
    int64_t last_rtcp_ntp_time;     // 上次发送SR的NTP时间
    int64_t first_rtcp_ntp_time;    // 首次SR的NTP时间
    int first_packet;               // 是否第一个包
    
    // 编解码器特定字段
    int max_frames_per_packet;  // 每包最大帧数 (AAC等)
    int num_frames;              // 当前包中的帧数
    int nal_length_size;        // NAL长度字段大小 (H.264/HEVC)
    int buffered_nals;           // 缓冲的NAL数 (H.264 STAP-A)
    
    int flags;  // RTP标志位
};

5.2 RTSPStream

位置 : libavformat/rtsp.h (部分字段)

c 复制代码
typedef struct RTSPStream {
    int stream_index;              // 对应的AVStream索引
    char control_url[256];         // RTSP控制URL
    AVFormatContext *transport_priv; // RTP Muxer Context
    int interleaved_min;            // RTP通道ID
    int interleaved_max;            // RTCP通道ID
    // ... 其他字段
} RTSPStream;

六、完整示例流程

6.1 H.264视频帧封装示例

假设有一个H.264 IDR帧,包含以下NAL单元:

  • SPS (7字节)
  • PPS (4字节)
  • IDR Slice (5000字节)

步骤1 : rtsp_write_packet() 接收AVPacket (包含完整帧数据)

步骤2 : ff_write_chained() 转换时间戳并调用RTP Muxer

步骤3 : rtp_write_packet() 调用 ff_rtp_send_h264_hevc()

步骤4: 解析NAL单元:

  • SPS: 7字节 → 单个RTP包 (Single NAL Unit)
  • PPS: 4字节 → 单个RTP包 (Single NAL Unit)
  • IDR Slice: 5000字节 → 分片为多个FU-A包

步骤5 : 每个RTP包调用 ff_rtp_send_data():

复制代码
RTP包1 (SPS):
  RTP Header: V=2, PT=96, Seq=100, TS=12345, SSRC=0x12345678
  Payload: SPS NAL单元 (7字节)

RTP包2 (PPS):
  RTP Header: V=2, PT=96, Seq=101, TS=12345, SSRC=0x12345678
  Payload: PPS NAL单元 (4字节)

RTP包3 (IDR Fragment 1):
  RTP Header: V=2, PT=96, Seq=102, TS=12345, SSRC=0x12345678, M=0
  Payload: FU Indicator (0x1C) + FU Header (0x65|0x80) + Fragment 1

RTP包4 (IDR Fragment 2):
  RTP Header: V=2, PT=96, Seq=103, TS=12345, SSRC=0x12345678, M=0
  Payload: FU Indicator (0x1C) + FU Header (0x65) + Fragment 2

RTP包5 (IDR Fragment 3):
  RTP Header: V=2, PT=96, Seq=104, TS=12345, SSRC=0x12345678, M=1
  Payload: FU Indicator (0x1C) + FU Header (0x65|0x40) + Fragment 3

6.2 AAC音频帧封装示例

假设有两个AAC ADTS帧:

  • Frame 1: 200字节
  • Frame 2: 180字节

步骤1-3: 同H.264示例

步骤4 : ff_rtp_send_aac() 处理:

  • 检查缓冲区空间
  • 添加AU Header: Size1=200, Size2=180
  • 聚合两个帧到一个RTP包

步骤5 : ff_rtp_send_data() 发送:

复制代码
RTP包:
  RTP Header: V=2, PT=97, Seq=200, TS=48000, SSRC=0x87654321, M=1
  Payload:
    AU Header Section:
      AU Header Length = 16 bits
      AU Header 1: Size=200 (13 bits), Index=0 (3 bits)
      AU Header 2: Size=180 (13 bits), Index=0 (3 bits)
    AU Payload Section:
      ADTS Frame 1 (200 bytes)
      ADTS Frame 2 (180 bytes)

七、关键参数和配置

7.1 包大小限制

c 复制代码
// 在rtp_write_header中计算
s->max_payload_size = s1->packet_size - 12;  // 减去RTP固定头12字节

// packet_size通常设置为MTU大小
// 以太网MTU = 1500字节
// IP头 = 20字节
// UDP头 = 8字节
// RTP头 = 12字节
// 实际Payload最大 = 1500 - 20 - 8 - 12 = 1460字节

7.2 时间戳计算

c 复制代码
// 音频: 基于采样率
avpriv_set_pts_info(st, 32, 1, st->codecpar->sample_rate);
// 例如: 48000Hz采样率,每采样时间戳增量为1

// 视频: 固定90000Hz
avpriv_set_pts_info(st, 32, 1, 90000);
// 每帧时间戳增量 = 90000 / 帧率
// 例如: 30fps, 每帧时间戳增量 = 90000 / 30 = 3000

7.3 Payload Type分配

c 复制代码
// 动态Payload Type范围: 96-127
// 静态Payload Type (RFC 3551):
//   - PCMU: 0
//   - PCMA: 8
//   - MPEG Audio: 14
//   - H.261: 31
//   - H.263: 34
//   - MPEG Video: 32
//   - H.264: 通常使用96 (动态)
//   - AAC: 通常使用97 (动态)

八、总结

RTSP推流中RTP包组装的完整流程:

  1. 入口 : rtsp_write_packet() 接收AVPacket
  2. 链式传递 : ff_write_chained() 转换时间戳并传递给RTP Muxer
  3. RTP封装 : rtp_write_packet() 根据编解码器类型调用特定封装函数
  4. 编解码器处理 :
    • H.264/HEVC: FU-A分片或STAP-A聚合
    • AAC: AU Header + 多帧聚合
  5. RTP头构建 : ff_rtp_send_data() 构建12字节RTP固定头
  6. 传输 :
    • UDP: 直接发送
    • TCP: 添加Interleaved头后发送

整个过程确保了:

  • 时间同步: 通过RTP时间戳和RTCP SR
  • 包完整性: 通过序列号检测丢包
  • MTU适配: 大包自动分片
  • 效率优化: 小包聚合减少开销
相关推荐
Likeadust3 小时前
视频直播点播平台EasyDSS构建安全高效的医疗培训直播新体系
安全·音视频
CV炼丹术3 小时前
告别注意力机制?MamEVSR:基于状态空间模型的事件视频超分新范式
音视频
酷柚易汛智推官3 小时前
从清影2.0看AIGC视频未来:技术、生态与可持续产业价值的竞争逻辑
aigc·音视频·酷柚易汛
python百炼成钢4 小时前
49.Linux音频驱动
android·linux·音视频
OpenCSG4 小时前
13.6B参数铸就“世界模型”,美团LongCat-Video实现5分钟原生视频生成,定义AI视频新标杆
人工智能·音视频
Blossom.1184 小时前
基于扩散模型的视频生成优化:从Stable Diffusion到AnimateDiff的显存革命
人工智能·深度学习·学习·决策树·搜索引擎·stable diffusion·音视频
赖small强4 小时前
【音视频开发】深度解析图像处理核心概念:饱和度、色度与对比度
图像处理·音视频·色度·对比度·饱和度
pu_taoc4 小时前
ffmpeg实战2-从MP4文件提取 音频和视频
c语言·c++·ffmpeg·音视频
summerkissyou19874 小时前
audio-audioflinger-应用音量到活跃流
android·音视频