RTP协议收发组件开发

本文探讨rtp协议相关知识。

rtp协议规范

复制代码
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 |P|X| CC    |M|      PT     |                SN            |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                              TS                              |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                              SSRC                            |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                       CSRC(0~15byte)                         |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|   Extened Identifier          |        Extened Length        |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                       Extened Date(...)                      |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                          Payload(...)                        |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|                  Padding Date(...)                    |PadLen|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

v:版本(固定为2)
P:1表示有填充,填充是为数据对齐,Padding Date为填充数据,PadLen(包尾) = Padding Date + 1
X:1表示有扩展,扩展是由于12字节头信息无法全部表示控制信息:帧类型(I/P/B),音量,声道,时间戳偏移,绝对时间,丢包去重信息,帧编号,加密信息
CC:CSRC标识符个数即多路数据源个数(多个音频混合数据)
M:1表示帧结束即帧最后一包数据,最后一包数据中包含pyload数据
PT:表示数据编码类型:95(h264),97(h265),8(pcma),9(g722)用于payload的数据解析
SN:rtp包序列号,连续递增
TS:采样时间戳,8000(音频),9000(视频)
SSRC:同步源标识符,随机生成,全局唯一,一个SSRC标识一路音频或视频流
CSRC:每个CSRC占用4字节,CSRC = CC * 4,不能拆封出payload中的数据,用于标识多路数据源的信息(有几人在说,分别是谁,音量多大)
Payload:载荷数据 

项目背景

RTP(实时传输协议)是音视频实时通信领域的核心协议,广泛应用于视频会议、直播、语音通话等场景。为满足实时数据传输的需求,独立设计并实现了一套轻量级、高性能的 RTP 协议收发组件,支持 RTP 数据包的封装、解析、发送和接收,适配嵌入式与通用服务器环境。

核心功能

协议层设计与封装: 严格遵循 RTP 协议 RFC3550 标准,定义RTP 固定头、扩展头数据结构,精准映射协议字段(版本号、填充位、扩展位、CSRC 计数、标记位、负载类型、序列号、时间戳、同步源标识等)。封装Rtp类,抽象初始化、发送、接收核心接口,私有化拷贝 / 赋值构造函数避免浅拷贝问题,保证类的封装性与安全性。

网络层实现: 基于UDP协议实现无连接的实时数据传输,完成RtpInit初始化接口:创建收发套接字、绑定端口/IP、填充发送端地址结构,支持本地回环与跨网络通信。

**数据包编解码核心逻辑:**发送端完成 RTP 包组装:依次填充固定头、CSRC 列表、扩展数据、负载数据、填充位,动态校验包长度(适配 MTU=1500 字节限制),避免数据包超限。支持可选扩展头、填充位配置,灵活适配不同音视频编码(如 H.264/OPUS)的传输需求。接收端逐字段解析 RTP 头、CSRC 列表、扩展数据、填充位、负载数据,层层校验数据长度与缓冲区大小,避免内存越界。处理异常场景:CSRC 计数超限、扩展数据长度异常、负载缓冲区不足、填充位长度非法等,返回明确的错误码,保证组件鲁棒性。

关键技术

高性能: 基于UDP无连接特性,减少 TCP 握手/重传开销,适配实时音视频低延迟需求;内存操作采用栈缓冲区(MTU=1500),避免堆内存频繁分配释放。

高可靠性: 全流程参数校验(CSRC 数量≤15、包长度≤MTU、缓冲区大小、填充位合法性),异常场景返回明确错误码,降低线上崩溃风险。

可扩展性: 支持扩展头自定义、负载类型灵活配置,可适配不同音视频编码格式的传输需求。

**跨平台兼容:**基于标准 Linux 网络 API 开发,可移植到嵌入式 Linux(如 ARM)、x86 服务器等环境。

项目成果:

测试用例验证通过,实现本地 RTP 数据包的稳定收发,负载数据解析准确率 100%。

组件轻量化、低耦合,可直接集成到音视频 SDK 中,为上层业务提供统一的 RTP 传输能力。

项目目录结构

文件简介

复制代码
rtp.h:rtp协议相关定义,包含错误号、rtp协议头、扩展数据头、rtp协议类(rtp初始化、rtp发包、rtp收包)

rtp.cpp:rtp协相关议实现rtp初始化实现,rtp发包实现、rtp收包实现

test.cpp:rtp收发包的测试

run.sh:编译运行脚本

rtp:可执行二进制程序

rtp.log:程序运行日志

rtp.h

复制代码
/* 
 * 变更历史: 
 * 2026-04-11  cxb  创建该文件. 
 */ 

/* 
 * @file  rtp.h 
 * @brief 
 * 功能:rtp协议收发数据. 
 */ 

#ifndef __RTP__H__
#define __RTP__H__

#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>

/* 网络数据包最大长度 */
#define MTU 1500

/* 错误号 */
typedef enum
{
        OK = 0,
        ERROR
} Status;

/* rtp协议头 */
struct RtpHeader
{
        unsigned char v:2;      /* rtp协议版本,默认为2 */
        unsigned char p:1;      /* 填充对齐标志 */
        unsigned char x:1;      /* 扩展数据标志 */
        unsigned char cc:4;     /* CSRC标识符个数 */
        unsigned char m:1;      /* 帧结束标志 */
        unsigned char pt:7;     /* 数据编码类型 */
        unsigned short sn;      /* rtp包序列号 */
        unsigned int ts;        /* 采样频率 */
        unsigned int ssrc;      /* 同步源标识符 */
};

/* rtp扩展头数据 */
struct RtpExtensionHeader
{
        unsigned short profile; /* 扩展标识*/
        unsigned short len;             /* 扩展数据长度 */
};

class Rtp
{
        public:

                /* rtp初始化 */
                Status RtpInit(const char *ip, const int port);

                /* 析构清空发送和接收包,数据包长度置0,发送和接收句柄置0 */
                Rtp()
                {
                        memset(SendPackBuf, 0, MTU);
                        memset(RecvPackBuf, 0, MTU);
                        PackLen = 0;
                        RtpSendFd = 0;
                        RtpRecvFd = 0;
                }

                /* 析构清空发送和接收包,数据包长度置0,关闭发送和接收句柄 */
                ~Rtp()
                {
                        memset(SendPackBuf, 0, MTU);
                        memset(RecvPackBuf, 0, MTU);
                        PackLen = 0;
                        close(RtpSendFd);
                        close(RtpRecvFd);
                }
              
                /* 接收rtp包 */
                Status RtpRecv(struct RtpHeader *hdr,
                                    unsigned int *csrc,
                                    unsigned int MaxCsrc,
                                    unsigned short *ExtenedIdentify,
                                    unsigned char *ExtenedData,
                                    unsigned int MaxExtenedLen,
                                    unsigned char *payload,
                                    unsigned int MaxPayload);

                /* 发送rtp包 */
                Status RtpSend(const unsigned char cc,
                                    const unsigned char m,
                                    const unsigned char pt,
                                    const unsigned short sn,
                                    const unsigned int ts,
                                    const unsigned int ssrc,
                                    const unsigned int *csrc,
                                    const unsigned char *payload,
                                    const unsigned int PayloadLen,
                                    const unsigned short profile,
                                    const unsigned char *ExtenedData,
                                    const unsigned int ExtenedLen,
                                    const unsigned int PadLen);


        private:
        unsigned char SendPackBuf[MTU]; /* 发送包 */
        unsigned char RecvPackBuf[MTU]; /* 接收包 */
                unsigned int PackLen;           /* 实际包长 */
                int RtpRecvFd;                  /* rtp接收句柄 */
                int RtpSendFd;                  /* rtp发送句柄 */
                struct sockaddr_in SendAddr;    /* rtp发送协议结构 */
                struct sockaddr_in RecvAddr;    /* rtp接收协议结构 */
         
                /* 禁止拷贝和赋值构造 */
                Rtp(const class Rtp &rtp);
                class Rtp operator=(const class Rtp &rtp);
};

#endif

rtp.cpp

复制代码
/* 
 * 变更历史: 
 * 2026-04-11  cxb  创建该文件. 
 */ 

/* 
 * @file  rtp.cpp 
 * @brief 
 * 功能:rtp协议的实现. 
 */

#include "rtp.h"
#include <sys/socket.h>
#include <netinet/in.h>

/* rtp初始化 */
Status Rtp::RtpInit(const char *ip, const int port)
{
        Status RetCode = OK;

        /* 创建发送句柄*/
        RtpSendFd = socket(AF_INET, SOCK_DGRAM, 0);
        if(RtpSendFd == -1)
        {
                RetCode = ERROR;
                return RetCode;
        }

        /* 填充协议ip和端口 */
        SendAddr.sin_family = AF_INET;
        SendAddr.sin_port = htons(port);
        inet_aton(ip, &SendAddr.sin_addr);

        /* 创建接收句柄 */
        RtpRecvFd = socket(AF_INET, SOCK_DGRAM, 0);
        if(RtpRecvFd == -1)
        {
                RetCode = ERROR;
                return RetCode;
        }

        /* 绑定端口和ip*/
        RecvAddr.sin_family = AF_INET;
        RecvAddr.sin_port = htons(port);
        RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        bind(RtpRecvFd, (struct sockaddr *)&RecvAddr, sizeof(RecvAddr));

        return RetCode;
}

/* 接收rtp数据包 */
Status Rtp::RtpRecv(struct RtpHeader *hdr,
                                        unsigned int *csrc,
                    unsigned int MaxCsrc,
                    unsigned short *ExtenedIdentify,
                    unsigned char *ExtenedData,
                    unsigned int MaxExtenedLen,
                    unsigned char *payload,
                    unsigned int MaxPayload)
{
    Status RetCode = OK;

        /* 接收数据包 */
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int RecvSize = recvfrom(RtpRecvFd, RecvPackBuf, sizeof(RecvPackBuf), 0, (struct sockaddr *)&src, &len);
        if(RecvSize == -1)
        {
                RetCode = ERROR;
                return RetCode;
        }

        /* 分解rtp协议头 */
    struct RtpHeader *buf = (struct RtpHeader *)RecvPackBuf;
    hdr->v = buf->v;
    hdr->p = buf->p;
    hdr->x = buf->x;
    hdr->cc = buf->cc;
    hdr->m  = buf->m;
    hdr->pt = buf->pt;
    hdr->sn = ntohs(buf->sn);
    hdr->ts = ntohl(buf->ts);
    hdr->ssrc = ntohl(buf->ssrc);

        /* csrc长度校验 */
    if(hdr->cc > 15 || hdr->cc > MaxCsrc)
    {
                RetCode = ERROR;
                return RetCode;
    }

        /* 分解csrc */
    unsigned char *ptr  = RecvPackBuf + sizeof(struct RtpHeader);
    for(int  Crsccount = 0; Crsccount < hdr->cc; Crsccount++)
    {
                csrc[Crsccount] = ntohl(*((unsigned int*)ptr));
                ptr += 4;
    }

        /* 分解扩展数据 */
    if(hdr->x)
    {
                /* 校验包长度异常 */
                if((ptr + 4) > (RecvPackBuf + RecvSize))
                {
                        RetCode = ERROR;
                        return RetCode;
                }

                /* 分解扩展头 */
                struct RtpExtensionHeader *reh = (struct RtpExtensionHeader *)ptr;
                *ExtenedIdentify = ntohs(reh->profile);
                int ExtenedLen = ntohs(reh->len);
                ptr += 4;

                /* 校验包长度异常 */
                if((ptr + ExtenedLen * 4) > (RecvPackBuf + RecvSize))
                {
                        RetCode = ERROR;
                        return RetCode;
                }

                /* 校验提取扩展数据空间是否足够 */
                if(ExtenedLen > MaxExtenedLen / 4)
                {
                        RetCode = ERROR;
                        return RetCode;
                }

                /* 提取扩展数据 */
                memcpy(ExtenedData, ptr, ExtenedLen * 4);
                ptr += (ExtenedLen * 4);

    }
    else
    {
                /* 无扩展数据 */
                *ExtenedIdentify = 0;
                memset(ExtenedData, 0, MaxExtenedLen * 4);
    }

        /* 分解填充数据 */
    int PadLen = 0;
    if(hdr->p)
    {
                /* 校验包长度异常 */
                if(RecvSize < 1)
                {
                        RetCode = ERROR;
                        return RetCode;
                }

                /* 获取填充长度 */
                PadLen = RecvPackBuf[RecvSize - 1];
                if(PadLen < 0 || PadLen >= RecvSize)
                {
                        RetCode = ERROR;
                        return RetCode;
                }
     }

         /* 分解pyload */
     int PayloadStart = ptr - RecvPackBuf;
     int PayloadEnd = RecvSize - PadLen;
     int PayloadLen = PayloadEnd - PayloadStart;
     if((PayloadLen < 0) || (PayloadLen > MaxPayload))
     {
                RetCode = ERROR;
                return RetCode;
     }

         /* 提取payload */
     memcpy(payload, RecvPackBuf + PayloadStart, PayloadLen);
     return RetCode;
}

/* 发送rtp数据包 */
Status Rtp::RtpSend(const unsigned char cc,
                                        const unsigned char m,
                                        const unsigned char pt,
                                        const unsigned short sn,
                                        const unsigned int ts,
                                        const unsigned int ssrc,
                                        const unsigned int *csrc,
                                        const unsigned char *payload,
                                        const unsigned int PayloadLen,
                                        const unsigned short profile,
                                        const unsigned char *ExtenedData,
                                        const unsigned int ExtenedLen,
                                        const unsigned int PadLen)
{
        Status RetCode = OK;

        /* 置0实际包长,清空发送包 */
        PackLen = 0;
        memset(SendPackBuf, 0, sizeof(SendPackBuf));

        /* 校验cc */
        if(cc > 15)
        {
                RetCode = ERROR;
                return RetCode;
        }

        /* 填充rtp头 */
        struct RtpHeader *hdr = (struct RtpHeader *)SendPackBuf;
        hdr->v = 2;
        hdr->p = (PadLen > 0) ? 1 : 0;
        hdr->x = (ExtenedLen > 0 && ExtenedData != nullptr) ? 1 : 0;
        hdr->cc = cc;
        hdr->m  = m;
        hdr->pt = pt;
        hdr->sn = htons(sn);
        hdr->ts = htonl(ts);
        hdr->ssrc = htonl(ssrc);

        /* 校验包长 */
        unsigned int PackSize = sizeof(struct RtpHeader) + cc * 4 + PayloadLen;
        if(PackSize > MTU)
        {
                RetCode = ERROR;
                return RetCode;
        }

        /* 填充csrc */
        unsigned char *ptr  = SendPackBuf + sizeof(struct RtpHeader);
        PackLen += sizeof(struct RtpHeader);
        for(int  Crsccount = 0; Crsccount < cc; Crsccount++)
        {
                *((unsigned int *)ptr) = htonl(csrc[Crsccount]);
                ptr += 4;
        }
        PackLen += (cc * 4);

        /* 填充扩展数据 */
        if(hdr->x)
        {
                struct RtpExtensionHeader *reh = (struct RtpExtensionHeader *)ptr;
                reh->profile = htons(profile);
                int Extened32 = (ExtenedLen + 3) / 4;
                reh->len = htons(Extened32);
                ptr += 4;
                PackLen += 4;
                memcpy(ptr, ExtenedData, ExtenedLen);
                ptr += Extened32 * 4;
                PackLen += (Extened32 * 4);
        }

        /* 填充payload */
        memcpy(ptr, payload, PayloadLen);
        ptr += PayloadLen;
        PackLen += PayloadLen;

        /* 对齐数据 */
        if(hdr->p && PadLen >= 1)
        {
                memset(ptr, 0, PadLen);
                ptr += (PadLen  - 1);
                *ptr = PadLen;
                                PackLen += PadLen;
        }

        /* 发送数据包 */
        int SendSize = sendto(RtpSendFd, SendPackBuf, PackLen, 0, (struct sockaddr*)&SendAddr, sizeof(SendAddr));
        if(SendSize == -1)
        {
                RetCode = ERROR;
                return RetCode;
        }

        return RetCode;
}

test.cpp

复制代码
/* 
 * 变更历史: 
 * 2026-04-11  cxb  创建该文件. 
 */ 

/* 
 * @file  test.cpp 
 * @brief 
 * 功能:rtp协议测试. 
 */

#include "rtp.h"
#include <thread>
#include <unistd.h>

Rtp rtp;

/* 发送rtp数据 */
void sendpack()
{
        std::cout << "rtp send start..." << std::endl;
        unsigned char pay[] = "hello rtp";
        unsigned int paylen = sizeof(pay);
        unsigned int csrc[] = {0xaa, 0xbb, 0xcc};
        unsigned int cc = 3;
        unsigned char exdata[] = {0x01, 0x02, 0x03, 0x04, 0x05};
        unsigned exlen = sizeof(exdata);
        rtp.RtpSend(cc, 1, 96, 1, 9000, 1, csrc, pay, paylen, 1, exdata, exlen, 3);
        std::cout << "rtp send end..." << std::endl;
}

/* 接收rtp数据 */
void recvpack()
{
        std::cout << "rtp recv start..." << std::endl;
        struct RtpHeader hdr;
        unsigned int csrc[15];
        unsigned short exprofile;
        unsigned char exdata[256];
        unsigned char pay[1500] = {0};
        Status ret = rtp.RtpRecv(&hdr, csrc, 15, &exprofile, exdata, sizeof(exdata), pay, sizeof(pay));
        if(ret == OK)
                std::cout << "rtp recv success:" << pay << std::endl;
        else
                std::cout << "rtp recv fail:" << std::endl;

        std::cout << "rtp recv end..." << std::endl;
}

int main()
{
        std::cout << "rtp init..." << std::endl;
        rtp.RtpInit("127.0.0.1", 9000);

        std::thread t1(recvpack);
        sleep(1);
        std::thread t2(sendpack);
        t1.join();
        t2.join();

        return 0;
}

run.sh

复制代码
#
#       简介:rtp测试程序编译脚本
#       平台:linux
#       可执行测试程序:trtp
#       测试log:rtp.log
#

#!/bin/bash

g++ test.cpp rtp.cpp -o rtp -lpthread

echo "================$(date)==================" >> rtp.log

./rtp >> rtp.log

rtp.log

复制代码
================2026年 04月 13日 星期一 20:06:04 CST==================
rtp init...
rtp recv start...
rtp send start...
rtp send end...
rtp recv success:hello rtp
rtp recv end...
相关推荐
Deitymoon2 小时前
linux——UDP编程
linux·网络·udp
华普微HOPERF2 小时前
如何善用Sub-GHz射频芯片,打造低功耗、高稳定的IoT通信网络?
网络·物联网
Amelio_Ming2 小时前
linux内存管理-页面回收之内核线程 kswapd (二)
linux·服务器
jf加菲猫2 小时前
第12章 数据可视化
开发语言·c++·qt·ui
wsoz2 小时前
Leetcode矩阵-day7
c++·算法·leetcode·矩阵
Emberone2 小时前
C++内存管理+模板初尝试:暴风雨前的尝试
c++
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 79. 单词搜索 | C++ 标准方向数组 DFS 与回溯
c++·leetcode·深度优先
忙什么果2 小时前
海洋遥感论文中常说的:in-situ数据和proxy
linux·运维·服务器
360智汇云2 小时前
从安全组到网络ACL:企业级网络隔离能力的进阶方案
网络·安全