rtp传输推流h265
文章目录
- rtp源码阅读
- 分析分支udp
- 开始
- get_h264_nalu
- rtp_send_h264_nalu
- rtp&h265
- RTP协议
- RTP打包h265
rtp源码阅读
参考
分析分支udp
- audio-and-video-development-master\rtsp_prj/test_set.c
- 使用2的,udp,rtp_h264_test()
开始
-
protocol.h修改ip和端口
-
初始化socket
-
/*** 通过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; // 成功返回 }
-
create_file_manger
- 打开264文件,记录fd,大小,分配缓存
-
rtp_header_init 填充信息
-
// head - 要初始化的RTP头部结构体指针 // payloadtype - RTP负载类型(如H264为96) // marker - 标记位(视频帧结束标志) // seq - 序列号(用于包排序) // timestamp - 时间戳(基于采样频率) // ssrc - 同步信源标识符(随机ID)
-
-
get_h264_nalu 获取一个nalu(每次读取4k,然后找00 00 00 01头)
- nalu就是2个00 00 00 01中间的数据
-
先发送发送SPS/PPS
- 就是01后面的第一个字节的低5位,7或者8(67/68)
-
后续继续发送,并且加时间挫,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协议
-
RTP协议是由RTP Header和RTP Payload两部分组成的
-
RTP Header
-
-
RTP头部前12个字节的含义是固定的,具体的含义如下所示
- V:RTP协议的版本号,占2位,当前协议版本号为2。
- P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
- X:扩展标志,占1位,如果X=1,则在RTP报头后有一个扩展头。
- CC:CSRC计数器,占4位,指示CSRC 标志符的个数。
- M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
- PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型。
- sequence number:序列号,占16位,用于表示发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包
- timestamp:相对时间戳,占32位,反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制
- SSRC:同步信源标识符,占32位,用于标志同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
- CSRC:特约信源标志符,每个CSRC标识符占32位,可以有0~15个。每个CSRC表示了包含在该RTP报文有效载荷中的所有特约信源
-
-
RTP Payload
-
单NALL包模式
-
- PayloadHdr:单包模式下其值为H.265的NALU Header。
- NAL unit payload data:负载数据。
-
NALU单元去掉开始头,直接作为RTP Payload加上RTP Header进行发送即可
-
[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…]
-
-
-
分包模式
-
- PayloadHdr:与H.265 NALU Header结构一致,只是修改了其中的Type类型,Type=49。
- FU header:具体组成如下所示
-
- S:1bit,值为1表示分包开始,其余情况为0。
- E:1bit,值为1表示分包结束,其余情况为0。
- FUType:等于H.265 NALU Header中的Type类型。
- (buf[0] >> 1) & 0x3F; //就是265的类型type
-
-
例子
-
[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;
}