当前位置: 首页 > news >正文

音视频入门基础:RTP专题(10)——FFmpeg源码中,解析RTP header的实现

一、引言

由《音视频入门基础:RTP专题(9)——FFmpeg接收RTP流的原理和内部实现》可以知道,FFmpeg接收RTP流时,其源码内部会调用rtp_read函数。而rtp_read函数内部会通过recvfrom函数接收基于UDP的RTP音视频数据。一般情况下,每通过一次recvfrom函数接收到数据算一个RTP packet。然后FFmpeg会通过rtp_parse_packet_internal函数解析该RTP packet。

二、rtp_parse_packet_internal函数的定义

rtp_parse_packet_internal函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/rtpdec.c中:

static int rtp_parse_packet_internal(RTPDemuxContext *s, AVPacket *pkt,
                                     const uint8_t *buf, int len)
{
    unsigned int ssrc;
    int payload_type, seq, flags = 0;
    int ext, csrc;
    AVStream *st;
    uint32_t timestamp;
    int rv = 0;

    csrc         = buf[0] & 0x0f;
    ext          = buf[0] & 0x10;
    payload_type = buf[1] & 0x7f;
    if (buf[1] & 0x80)
        flags |= RTP_FLAG_MARKER;
    seq       = AV_RB16(buf + 2);
    timestamp = AV_RB32(buf + 4);
    ssrc      = AV_RB32(buf + 8);
    /* store the ssrc in the RTPDemuxContext */
    s->ssrc = ssrc;

    /* NOTE: we can handle only one payload type */
    if (s->payload_type != payload_type)
        return -1;

    st = s->st;
    // only do something with this if all the rtp checks pass...
    if (!rtp_valid_packet_in_sequence(&s->statistics, seq)) {
        av_log(s->ic, AV_LOG_ERROR,
               "RTP: PT=%02x: bad cseq %04x expected=%04x\n",
               payload_type, seq, ((s->seq + 1) & 0xffff));
        return -1;
    }

    if (buf[0] & 0x20) {
        int padding = buf[len - 1];
        if (len >= 12 + padding)
            len -= padding;
    }

    s->seq = seq;
    len   -= 12;
    buf   += 12;

    len   -= 4 * csrc;
    buf   += 4 * csrc;
    if (len < 0)
        return AVERROR_INVALIDDATA;

    /* RFC 3550 Section 5.3.1 RTP Header Extension handling */
    if (ext) {
        if (len < 4)
            return -1;
        /* calculate the header extension length (stored as number
         * of 32-bit words) */
        ext = (AV_RB16(buf + 2) + 1) << 2;

        if (len < ext)
            return -1;
        // skip past RTP header extension
        len -= ext;
        buf += ext;
    }

    if (s->handler && s->handler->parse_packet) {
        rv = s->handler->parse_packet(s->ic, s->dynamic_protocol_context,
                                      s->st, pkt, &timestamp, buf, len, seq,
                                      flags);
    } else if (st) {
        if ((rv = av_new_packet(pkt, len)) < 0)
            return rv;
        memcpy(pkt->data, buf, len);
        pkt->stream_index = st->index;
    } else {
        return AVERROR(EINVAL);
    }

    // now perform timestamp things....
    finalize_packet(s, pkt, timestamp);

    return rv;
}

该函数的作用是:解析一个RTP packet。该函数的前半部分实现了解析该RTP packet的RTP header的功能。

形参s:既是输入型参数也是输出型参数,指向一个RTPDemuxContext类型变量。RTPDemuxContext结构体声明如下,存贮RTP解复用的上下文信息:

struct RTPDemuxContext {
    AVFormatContext *ic;
    AVStream *st;
    int payload_type;
    uint32_t ssrc;
    uint16_t seq;
    uint32_t timestamp;
    uint32_t base_timestamp;
    int64_t  unwrapped_timestamp;
    int64_t  range_start_offset;
    int max_payload_size;
    /* used to send back RTCP RR */
    char hostname[256];

    int srtp_enabled;
    struct SRTPContext srtp;

    /** Statistics for this stream (used by RTCP receiver reports) */
    RTPStatistics statistics;

    /** Fields for packet reordering @{ */
    int prev_ret;     ///< The return value of the actual parsing of the previous packet
    RTPPacket* queue; ///< A sorted queue of buffered packets not yet returned
    int queue_len;    ///< The number of packets in queue
    int queue_size;   ///< The size of queue, or 0 if reordering is disabled
    /*@}*/

    /* rtcp sender statistics receive */
    uint64_t last_rtcp_ntp_time;
    int64_t last_rtcp_reception_time;
    uint64_t first_rtcp_ntp_time;
    uint32_t last_rtcp_timestamp;
    int64_t rtcp_ts_offset;

    /* rtcp sender statistics */
    unsigned int packet_count;
    unsigned int octet_count;
    unsigned int last_octet_count;
    int64_t last_feedback_time;

    /* dynamic payload stuff */
    const RTPDynamicProtocolHandler *handler;
    PayloadContext *dynamic_protocol_context;
};

形参pkt:输出型参数。执行rtp_parse_packet_internal函数后,pkt会得到从该RTP packet解析出来的信息。

形参buf:输入型参数,存放需要被解析的该RTP packet数据的缓冲区。

形参len:输入型参数,该RTP packet的字节数。

返回值:返回非负数表示成功,返回负数表示失败。

三、rtp_parse_packet_internal函数中,解析RTP header的实现

rtp_parse_packet_internal函数中,首先通过下面语句将RTP header中的CSRC count字段读取出来:

    csrc         = buf[0] & 0x0f;

将RTP header中的extension字段读取出来:

    ext          = buf[0] & 0x10;


 

将RTP header中的payload type(有效载荷类型)字段读取出来:

    payload_type = buf[1] & 0x7f;

判断RTP header中的marker字段值的值是否为1,如果为1,设置局部变量flags:

    if (buf[1] & 0x80)
        flags |= RTP_FLAG_MARKER;

将RTP header中的sequence number(序列号)字段读取出来。关于AV_RB16宏定义的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:

    seq       = AV_RB16(buf + 2);
//...
    s->seq = seq;

将RTP header中的timestamp(时间戳)字段读取出来:

    timestamp = AV_RB32(buf + 4);

将RTP header中的synchronization source (SSRC) identifier字段读取出来:

    ssrc      = AV_RB32(buf + 8);
    /* store the ssrc in the RTPDemuxContext */
    s->ssrc = ssrc;

判断RTP header中的padding属性的值是否为1,如果为1,表示RTP packet末端会附加填充字节:

    if (buf[0] & 0x20) {
        int padding = buf[len - 1];
        if (len >= 12 + padding)
            len -= padding;
    }

RTP header中的RTP Fixed Header(RTP header中的固定长度部分)固定占12字节,RTP Fixed Header之后紧接着的是contributing source(CSRC) identifiers,让指针buf移动到contributing source(CSRC) identifiers之后,使得可以读取之后的数据:

    len   -= 12;
    buf   += 12;

    len   -= 4 * csrc;
    buf   += 4 * csrc;
    if (len < 0)
        return AVERROR_INVALIDDATA;

如果RTP header中存在RTP Header Extension,跳过RTP Header Extension的读取:

    /* RFC 3550 Section 5.3.1 RTP Header Extension handling */
    if (ext) {
        if (len < 4)
            return -1;
        /* calculate the header extension length (stored as number
         * of 32-bit words) */
        ext = (AV_RB16(buf + 2) + 1) << 2;

        if (len < ext)
            return -1;
        // skip past RTP header extension
        len -= ext;
        buf += ext;
    }

相关文章:

  • Docker仿真宇树狗GO1
  • Spring Security+JWT+Redis实现项目级前后端分离认证授权
  • 【DeepSeek-R1背后的技术】系列九:MLA(Multi-Head Latent Attention,多头潜在注意力)
  • 深入解析适配器模式:软件架构中的接口协调大师
  • printf和 vprintf的区别
  • MongoDB学习
  • CASS11快捷键设置
  • 国内三大知名开源批发订货系统对比
  • 【React】React 基础(2)
  • 深度解读DeepSeek:从原理到模型
  • Cursor不能白嫖还不安全:Cline + DeepSeek V3,最强国产双开源解决方案
  • C语言内存函数
  • 【MATLAB例程】RSSI/PLE定位与卡尔曼滤波NLOS抑制算法,附完整代码
  • 智能自动化新纪元:AI与UiPath RPA的协同应用场景与技术实践
  • vscode软件中引入vant组件
  • leetcode hot100-34 合并K个升序链表
  • 什么是Firehose?它的作用是什么?
  • 蓝桥杯笔记——递归递推
  • FTP 实验(ENSP模拟器实现)
  • 力扣-贪心-53 最大子数组和
  • 哥伦比亚总统称将在访华期间签署“一带一路”倡议意向书,外交部回应
  • 科技日报刊文批院士专家“赶场式”跑会:助长浮躁之气功利之心
  • 黔西市游船倾覆事故发生后,贵州省气象局进入特别工作状态
  • 美国加州州长:加州继续对中国“敞开贸易大门”
  • 多地晒五一假期前两日成绩单,湖南单日客流同比增长逾三成
  • 三亚回应“游客骑摩托艇出海遇暴雨”:未失联,已引导申请先行赔付