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

rtp传输推流h265

文章目录

  • rtp源码阅读
    • 分析分支udp
      • 开始
        • get_h264_nalu
        • rtp_send_h264_nalu
  • rtp&h265
    • RTP协议
  • RTP打包h265

rtp源码阅读

参考

分析分支udp

  1. audio-and-video-development-master\rtsp_prj/test_set.c
  2. 使用2的,udp,rtp_h264_test()

开始

  1. protocol.h修改ip和端口

  2. 初始化socket

  3. /*** 通过RTP协议播放H.264视频流* * @param sockfd 套接字文件描述符,用于网络传输* @param client 客户端信息结构体指针* @return 成功返回0,失败返回-1*/
    int rtp_play_h264(int sockfd, client_t *client) {// 创建H264文件管理器,以只读方式打开文件file_t *file = create_file_manger(H264_FILE, "r");if (file == NULL) return -1;  // 文件打开失败则返回错误//printf("h264 file size: %d\n", file->filesize);// 分配RTP数据包内存并初始化RTP头struct rtp_packet *packet = (struct rtp_packet *)malloc(RTP_PACKET_SIZE);rtp_header_init(&packet->header, RTP_PAYLOAD_TYPE_H264, 0, 0, 0, 0x88923423);//printf("version:%d payloadtype:%d\n", packet->header.version, packet->header.payloadtype);// 分配NAL单元缓冲区(1MB大小)uint8_t *nalu = malloc(1024*1024);int nalulen, index = 0;  // NALU长度和帧计数器int got_sps_pps = 0;     // 标记是否已发送SPS/PPS// 循环读取文件直到结束while (!file_search_finish(file)) {// 获取H.264的NALU单元int codelen = get_h264_nalu(file, nalu, &nalulen);// 优先发送SPS/PPS(序列参数集和图像参数集)unsigned char nal_type = nalu[0] & 0x1f;  // 获取NALU类型(低5位)if (!got_sps_pps) {if (nal_type == 7 || nal_type == 8) { // 7=SPS, 8=PPS(67/68)// 发送SPS或PPS NALUrtp_send_h264_nalu(sockfd, client, nalu, nalulen, packet);got_sps_pps = 1;  // 标记已发送SPS/PPScontinue;         // 继续处理下一个NALU} else {// 如果未收到SPS/PPS,则跳过当前NALU(可能需要重新查找文件开头)continue;}}// 错误处理if (codelen < 0) {break;  // 读取错误则退出循环} // 如果是新帧的开始(codelen=4表示帧起始码长度)else if (codelen == 4) { index++;  // 帧计数器递增//printf("read %4d rtp frame:%d\n", index, nalulen);usleep(1000*1000/H264_FPS);  // 根据帧率控制播放速度}// 发送当前NALU单元rtp_send_h264_nalu(sockfd, client, nalu, nalulen, packet);// 如果不是SPS/PPS,则更新时间戳(按帧率计算)if ((nalu[0] & 0x1f) != 7 && (nalu[0] & 0x1f) != 8) {packet->header.timestamp += 90000/H264_FPS;  // 90000是RTP时钟频率}}// 释放资源free(packet);free(nalu);destroy_file_manager(file);return 0;  // 成功返回
    }
    
    1. create_file_manger

      1. 打开264文件,记录fd,大小,分配缓存
    2. rtp_header_init 填充信息

      1. //   head       - 要初始化的RTP头部结构体指针
        //   payloadtype - RTP负载类型(如H264为96)
        //   marker     - 标记位(视频帧结束标志)
        //   seq        - 序列号(用于包排序)
        //   timestamp  - 时间戳(基于采样频率)
        //   ssrc       - 同步信源标识符(随机ID)
        
    3. get_h264_nalu 获取一个nalu(每次读取4k,然后找00 00 00 01头)

      1. nalu就是2个00 00 00 01中间的数据
    4. 先发送发送SPS/PPS

      1. 就是01后面的第一个字节的低5位,7或者8(67/68)
    5. 后续继续发送,并且加时间挫,65 … (IDR帧

get_h264_nalu
/*** @brief 从H.264文件中提取一个NALU单元* @param file 文件管理器指针,包含文件状态和缓冲区* @param nalu 输出缓冲区,用于存储提取的NALU数据* @param nalulen 输出参数,返回NALU数据的实际长度* @return 起始码长度(3或4),失败返回-1* * 函数工作原理:* 1. 使用滑动窗口机制读取文件(每次4KB)* 2. 查找起始码(0x000001或0x00000001)* 3. 定位下一个起始码作为当前NALU的结束位置* 4. 提取起始码之间的有效载荷数据*/
int get_h264_nalu(file_t *file, uint8_t *nalu, int *nalulen) {static int count = 4*1024;    // 文件读取块大小(4KB)int last_pos = file->searchpos; // 记录当前搜索起始位置/* 首次读取处理:填充文件缓冲区 */if (file->readpos == 0) {size_t read_bytes = fread(file->filebuf, 1, count, file->fp);file->readpos += read_bytes;  // 更新已读取位置}/* 起始码检测 */int codelen = startcode_len(&file->filebuf[last_pos]);//00 00 00 01if (codelen != 3 && codelen != 4) {fprintf(stderr, "[Error] Invalid start code at position %d\n", last_pos);return -1;}/* 跳过起始码,准备搜索下一个起始码 */file->searchpos += codelen;int found = 0;/* 搜索下一个起始码 */while (!found && !file_search_finish(file)) {/* 在当前缓冲区中搜索 */if (file->searchpos < file->readpos) {/* 文件末尾检查 */if (file->searchpos + 4 >= file->filesize) {file->searchpos = file->filesize;break;}/* 检测起始码 */if (startcode_len(&file->filebuf[file->searchpos]) > 0) {found = 1;} else {file->searchpos++;  // 未找到则继续移动指针}} /* 需要读取更多数据 */else {size_t remain = file->filesize - file->readpos;if (remain > 0) {/* 计算实际需要读取的大小 */size_t read_size = (remain > count) ? count : remain;size_t read_bytes = fread(&file->filebuf[file->readpos], 1, read_size, file->fp);if (read_bytes > 0) {file->readpos += read_bytes;} else {file->searchpos = file->filesize;  // 读取失败视为文件结束break;}} else {file->searchpos = file->filesize;  // 已到文件末尾break;}}}/* 计算NALU长度(排除起始码) */*nalulen = file->searchpos - last_pos - codelen;/* 有效性检查 */if (*nalulen <= 0 || *nalulen > MAX_NALU_SIZE) {fprintf(stderr, "[Error] Invalid NALU size: %d\n", *nalulen);return -1;}/* 拷贝有效NALU数据(跳过起始码) */memcpy(nalu, &file->filebuf[last_pos + codelen], *nalulen);/* 调试信息 */uint8_t nal_type = nalu[0] & 0x1F;  // H.264 NALU类型获取printf("[Debug] NALU extracted: type=%d, size=%d, startcode=%d\n", nal_type, *nalulen, codelen);return codelen;
}
rtp_send_h264_nalu
/*** 通过RTP协议发送H.264 NALU单元* * @param sockfd 套接字文件描述符,用于网络传输* @param client 客户端信息结构体指针* @param nalu H.264 NALU单元数据指针* @param nalulen NALU单元长度* @param packet RTP数据包结构体指针* @return 成功返回发送的总字节数,失败返回-1*/
int rtp_send_h264_nalu(int sockfd, client_t *client, uint8_t *nalu, int nalulen, struct rtp_packet *packet) {int send_bytes = 0;          // 记录已发送字节数uint8_t nalu_hdr = nalu[0];  // 提取NALU头(第一个字节)// 情况1:NALU长度小于等于RTP最大包大小,可以直接发送if (nalulen <= RTP_MAX_PTK_SIZE) {// 数据包结构:[12字节RTP头][完整NALU]memcpy(packet->payload, nalu, nalulen);  // 复制NALU到RTP负载// 发送RTP包(包含RTP头和NALU数据)int ret = rtp_send_packet(sockfd, client->ip, client->rtp_port, packet, nalulen+RTP_HEADER_SIZE);if (ret < 0) return -1;  // 发送失败返回错误packet->header.seq++;     // 序列号递增send_bytes += ret;       // 累加已发送字节数} // 情况2:NALU长度超过RTP最大包大小,需要分片发送else {// 计算分片参数(去掉NALU头后的有效负载长度)int t = nalulen - 1;  // payload长度(不包括nalu头部)int ptk_cnt = t / RTP_MAX_PTK_SIZE;  // 计算完整分片个数if (t % RTP_MAX_PTK_SIZE != 0) ptk_cnt += 1;  // 有余数则增加一个分片int nalu_pos = 1;  // 数据位置(跳过nalu头部)// 发送前N-1个分片for (int i = 0; i < ptk_cnt - 1; i++) {// 设置分片信息(第一个分片标记为START,中间分片标记为COMMON)if (i == 0) load_fragment_msg(packet, nalu_hdr, RTP_FRAG_TYPE_START);else load_fragment_msg(packet, nalu_hdr, RTP_FRAG_TYPE_COMMON);// 发送当前分片int ret = rtp_send_fragment(sockfd, client, &nalu[nalu_pos], RTP_MAX_PTK_SIZE, packet);if (ret < 0) return -1;  // 发送失败返回错误send_bytes += ret;       // 累加已发送字节数nalu_pos += RTP_MAX_PTK_SIZE;  // 移动数据位置}// 发送最后一个分片(标记为END)load_fragment_msg(packet, nalu_hdr, RTP_FRAG_TYPE_END);int ret = rtp_send_fragment(sockfd, client, &nalu[nalu_pos], nalulen-nalu_pos, packet);if (ret < 0) return -1;send_bytes += ret;}return send_bytes;  // 返回总共发送的字节数
}

rtp&h265

实时传输协议,由IETF的多媒体传输工作小组发布的网络传输协议,标准为RFC3550/3551。RTP协议支持TCP和UDP两种传输方式,RTP协议负责对流媒体数据进行封包并实现媒体流的实时传输,但并不能为按顺序传送的数据包提供可靠的传送机制,也不提供流量和拥塞控制,这些是依靠RTCP协议来完成的,两者配合使用。本文主要从数据处理的角度实现对H.265的RTP封装进行详细介绍

原文链接:https://blog.csdn.net/m0_60259116/article/details/124889465

RTP协议

  1. RTP协议是由RTP Header和RTP Payload两部分组成的

  2. RTP Header

    1. 在这里插入图片描述

    2. RTP头部前12个字节的含义是固定的,具体的含义如下所示

      1. V:RTP协议的版本号,占2位,当前协议版本号为2。
      2. P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
      3. X:扩展标志,占1位,如果X=1,则在RTP报头后有一个扩展头。
      4. CC:CSRC计数器,占4位,指示CSRC 标志符的个数。
      5. M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
      6. PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型。
      7. sequence number:序列号,占16位,用于表示发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包
      8. timestamp:相对时间戳,占32位,反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制
      9. SSRC:同步信源标识符,占32位,用于标志同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
      10. CSRC:特约信源标志符,每个CSRC标识符占32位,可以有0~15个。每个CSRC表示了包含在该RTP报文有效载荷中的所有特约信源
  3. RTP Payload

    1. 单NALL包模式

      1. 在这里插入图片描述

        1. PayloadHdr:单包模式下其值为H.265的NALU Header。
        2. NAL unit payload data:负载数据。
      2. NALU单元去掉开始头,直接作为RTP Payload加上RTP Header进行发送即可

        1. [00 00 00 01 40 01 0c 01 ff ff 01 60 70 82…]
          [RTP Header] [40 01 0c 01 ff ff 01 60 70 82…]
          
    2. 分包模式

      1. 在这里插入图片描述

        1. PayloadHdr:与H.265 NALU Header结构一致,只是修改了其中的Type类型,Type=49。
        2. FU header:具体组成如下所示
          1. 在这里插入图片描述

            1. S:1bit,值为1表示分包开始,其余情况为0。
            2. E:1bit,值为1表示分包结束,其余情况为0。
            3. FUType:等于H.265 NALU Header中的Type类型。
              1. (buf[0] >> 1) & 0x3F; //就是265的类型type
      2. 例子

        1. [00 00 00 01 26 01 af 1d 78 06 90 …]  //type = 0x13
          开始包:
          [RTP Header] [62 01 93 …]
          中间包:
          [RTP Header] [62 01 13 …]
          结束包:
          [RTP Header] [62 01 53 …]
          

RTP打包h265

下面是打包一个NALU的代码

static int rtp_send_h264_nalu(int sockfd, client_t* client, uint8_t* nalu, int nalulen, struct rtp_packet* packet) {int send_bytes = 0;uint8_t nal_type = (nalu[0] >> 1) & 0x3f;uint8_t buf[3];int ret;if (nalulen <= RTP_MAX_PTK_SIZE) {memcpy(packet->payload, nalu, nalulen);ret = rtp_send_packet(sockfd, client->ip, client->rtp_port, packet, nalulen + RTP_HEADER_SIZE);if (ret < 0) return -1;packet->header.seq++;send_bytes += ret;}else {buf[0] = 49 << 1;buf[1] = 1;buf[2] = nal_type;buf[2] |= 0x80;int payload_offset = 2;int remaining_len = nalulen - 2;int ptk_cnt = remaining_len / (RTP_MAX_PTK_SIZE - 3);if ((remaining_len % (RTP_MAX_PTK_SIZE - 3)) != 0)ptk_cnt++;for (int i = 0;i < ptk_cnt;i++) {int frag_len = (i == ptk_cnt - 1) ? remaining_len : RTP_MAX_PTK_SIZE - 3;if (i == ptk_cnt - 1) {buf[2] = (buf[2] & 0x7f) | 0x40;}else if (i > 0) {buf[2] &= 0x1f;}memcpy(packet->payload, buf, 3);memcpy(packet->payload + 3, nalu + payload_offset, frag_len);ret = rtp_send_packet(sockfd, client->ip, client->rtp_port, packet, frag_len + 3 + RTP_HEADER_SIZE);packet->header.seq++;send_bytes += ret;payload_offset += frag_len;remaining_len -= frag_len;}}return send_bytes;
}
http://www.dtcms.com/a/284410.html

相关文章:

  • Unity使用GTCRN实现流式语音增强
  • SpringBoot一Web Flux、函数式Web请求的使用、和传统注解@Controller + @RequestMapping的区别
  • 探微“元宇宙”:概念内涵、形态发展与演变机理
  • CSS面试题及详细答案140道之(41-60)
  • Kiro AI IDE上手初体验!亚马逊出品,能否撼动Cursor的王座?
  • Amazon S3成本优化完全指南:从入门到精通
  • 8 几何叠加分析
  • 系统设计时平衡超时时间与多因素认证(MFA)带来的用户体验下降
  • 量子计算的安全与伦理:当技术革命叩击数字时代的潘多拉魔盒
  • sqli-labs靶场通关笔记:第25-26a关 and、or、空格和注释符多重过滤
  • 4G模块 A7680通过MQTT协议连接到腾讯云
  • AI赋能Baklib,重塑企业知识管理与客户支持方式
  • Curr. Res. Food Sci.|福州大学吕旭聪团队:富硒鼠李糖乳杆菌GG重塑肠-肝轴,显著缓解酒精性肝损伤
  • 网络通信之基础知识
  • deep learning(李宏毅)--(六)--loss
  • day19-四剑客与正则-特殊符号正则-awk
  • [yotroy.cool] 记一次 Git 移除某个不该提交的文件
  • iOS WebView 调试与性能优化 跨平台团队高效协作方法解析
  • PyTorch生成式人工智能(18)——循环神经网络详解与实现
  • 可视化图解算法56:岛屿数量
  • Word 中为什么我的图片一拖就乱跑,怎么精确定位?
  • python使用pymysql库
  • modbus 校验
  • 泛型与类型安全深度解析及响应式API实战
  • Java 集合框架详解:Collection 接口全解析,从基础到实战
  • 7月17日日记
  • 【机器学习】向量数据库选型指南:企业内网部署场景
  • 从零开始:C++ UDP通信实战教程
  • 河南萌新联赛2025第(一)场:河南工业大学(补题)
  • SQLite的可视化界面软件的安装