定制化网站,Md5(Wordpress)解密,wordpress暂停网站,kn95口罩RTP协议 1. 概述1.1 RTP协议1.2 RTP和UDP的关系 2. RTP打包H264码流2.1 RTP单一传输2.2 RTP分片传输2.3 RTP多片合入传输 3.工程3.1 头文件3.1.1 rtp.h3.1.2 utils.h 3.2 cpp文件3.2.1 rtp.cpp3.2.2 utils.cpp 4.测试5.小结 参考#xff1a; 视音频数据处理入门#xff1a;UD… RTP协议 1. 概述1.1 RTP协议1.2 RTP和UDP的关系 2. RTP打包H264码流2.1 RTP单一传输2.2 RTP分片传输2.3 RTP多片合入传输 3.工程3.1 头文件3.1.1 rtp.h3.1.2 utils.h 3.2 cpp文件3.2.1 rtp.cpp3.2.2 utils.cpp 4.测试5.小结 参考 视音频数据处理入门UDP-RTP协议解析 从零开始写一个RTSP服务器三RTP传输H.264
1. 概述
1.1 RTP协议
RTPReal-time Transport Protocol实时传输协议是一种网络协议用于在IP网络上传输实时数据如音频、视频等。它的主要目的是提供一种可靠的、面向数据包的传输机制以支持实时多媒体应用。
RTP协议的特点包括
无连接 RTP协议本身不保证数据的可靠传输它只是负责将数据包从发送端发送到接收端而不关心数据包是否按顺序到达或者是丢失面向数据包 RTP协议适用于传输数据包而不是连续的数据流。这意味着它可以处理任意大小的数据包而不需要预先建立连接时间戳 每个RTP数据包都包含一个时间戳字段表示该数据包的发送时间。这有助于接收端重新组装和播放数据包以保持正确的播放顺序序列号 每个RTP数据包都有一个序列号用于标识数据包的顺序。接收端可以根据序列号对数据包进行排序以确保它们按照正确的顺序被处理同步源SSRC 每个RTP会话有唯一的同步源标识符SSRC用于区分不同的发送者。这有助于接收端识别并处理来自不同发送者的数据包扩展头 RTP协议支持扩展头允许在数据包中添加额外的信息如编解码器信息、载荷类型等
RTP头的格式为
名称表示内容占用比特备注V版本号2表示RTP的版本P填充标志1如果设置表示在数据包尾部具有一定的填充字节X扩展标志1如果设置表示在固定数据头部之后还有一个扩展头部CCCSRC计数4表示CSRC贡献源标识符的数量M标记1用于特定的标识符为1时表示一帧的结束PT有效载荷类型payload type7表示数据包中的负载类型例如H264格式JPEG格式Sequence number序列号16标识数据包的计数可用于检测是否存在丢失或错序timestamp时间戳321.时间同步接收端要知道每个数据包的发送时间以便正确地播放音频或视频。通过使用时间戳接收端可以准确地将数据包按照到达的顺序进行排序和播放 2.抖动控制如果收到乱序的数据包时间戳能够帮助接收端识别并处理这些乱序的数据包从而减少播放时的延时SSRC同步源标识符32用于唯一地表示一个RTP会话中的发送端。每个RTP流都有一个唯一的SSRC值从而区分不同的发送端防止冲突CSRC贡献源标识符列表(0~15)*32用于标识参与多传播的源。一个数据包可能由多个源发送CSRC字段允许接收端知道有哪些源参与了该数据包的生成也能够统计相关的信息检查这个数据包的来源是否是合法的
RTP协议通常与RTCPReal-time Transport Control Protocol实时传输控制协议一起使用。RTCP用于监控RTP会话的质量收集统计信息并提供反馈给发送端。RTCP报告包括发送方报告SR、接收方报告RR、源描述SDES和应用程序特定功能APP
1.2 RTP和UDP的关系
RTP和UDPUser Datagram Protocol用户数据报协议是两个不同的网络协议但它们之间存在密切的关系。RTP负责定义音视频数据包的格式、顺序、时间戳等参数以保证音视频数据在网络中的实时传输。RTP本身不提供任何传输可靠性只负责数据的封装和传输。UDP是一种无连接的传输层协议它提供了一种简单地、不可靠的数据传输服务。UDP协议将数据打包成数据报通过IP网络进行传输。由于UDP没有建立连接的过程所以它的传输速度比较快但同时也无法保证数据传输的可靠性
RTP和UDP之间的关系在于RTP通常使用UDP作为其底层传输协议。RTP数据包被封装在UDP数据报中进行传输以利用UDP的高效传输特性。同时RTP本身不关心数据传输的可靠性和顺序这些由下层的UDP和IP协议来处理。借用一下其他文中的图片流媒体协议栈如下所示 RTP之所以会使用UDP而不是TCP是因为RTP主要用于实时音视频传输这种应用场景对传输延迟非常敏感。TCP是一种面向连接的可靠性传输它通过重传机制来保证数据的完整性和可靠性但这也引入了额外的延迟相对比而言UDP速度更快更适合音视频传输的需求。所以RTP和UDP之间是互相配合的关系。
综上所述RTP负责音视频数据的封装和传输而UDP则提供了一种高效的传输服务。通过将RTP数据包封装在UDP数据包中可以实现音视频数据在网络中的实时传输
2. RTP打包H264码流
在进行RTP打包H264码流时因为是传输协议需要考虑每个数据包的大小。在网络传输中一般的最大传输单元Maximum Transmission UnitMTU的大小是1500字节TCP/IP协议栈如IP头占20字节UDP头占8字节RTP头占12字节所以RTP数据包最大为1460字节但是为了适应网络条件或避免分片可能也会使用相对较小的数据包例如1400字节。概括来说RTP打包的格式如下
// 使用最大的RTP Payload情况即RTP Payload1460 Bytes
-------------------------------------------------
| IP Header | UDP Header | RTP Header | RTP Payload |
-------------------------------------------------
| 20 Bytes | 8 Bytes | 12 Bytes | 1460 Bytes |
-------------------------------------------------这里不记录IP Header和UDP Header的信息仅考虑RTP的处理。其中RTP Header就是前面记录的HeaderRTP Payload就是具体的信息。这里会引入一个新的问题传输的数据量pkt_size和载荷大小rtp_payload之间的关系 1如果pkt_size等于设置的rtp_payload大小则一个RTP包携带一份数据 2如果pkt_size大于了rtp_payload需要将pkt分成片段来进行传输 3如果pkt_size小于了rtp_payload可以将若干个pkt合并到一个RTP当中
2.1 RTP单一传输
RTP单一传输最为简单其传输的格式为
--------------------------------------------------
| IP Header | UDP Header | RTP Header | RTP Payload |
--------------------------------------------------
| 20 Bytes | 8 Bytes | 12 Bytes | payload_size |
--------------------------------------------------对于将H264码流打包的情况这里的RTP Payload就是H264码流的NALU。其中RTP Payload的格式为 0 1 2 30 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--------------------------------|F|NRI| type | |-------- || || Bytes 2..n of a Single NAL unit || || ----------------| :...OPTIONAL RTP padding |--------------------------------第一个字节描述了这个NALU的总体信息 1Fforbidden_zero_bit默认为0 2NRI 表示NALU的重要程度越大则越重要最大为3 3type表示NALU的类型 这里的type的描述类型包括
#define NAL_UNIT_TYPE_UNSPECIFIED 0 // Unspecified
#define NAL_UNIT_TYPE_CODED_SLICE_NON_IDR 1 // Coded slice of a non-IDR picture
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_A 2 // Coded slice data partition A
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_B 3 // Coded slice data partition B
#define NAL_UNIT_TYPE_CODED_SLICE_DATA_PARTITION_C 4 // Coded slice data partition C
#define NAL_UNIT_TYPE_CODED_SLICE_IDR 5 // Coded slice of an IDR picture
#define NAL_UNIT_TYPE_SEI 6 // Supplemental enhancement information (SEI)
#define NAL_UNIT_TYPE_SPS 7 // Sequence parameter set
#define NAL_UNIT_TYPE_PPS 8 // Picture parameter set
#define NAL_UNIT_TYPE_AUD 9 // Access unit delimiter
#define NAL_UNIT_TYPE_END_OF_SEQUENCE 10 // End of sequence
#define NAL_UNIT_TYPE_END_OF_STREAM 11 // End of stream
#define NAL_UNIT_TYPE_FILLER 12 // Filler data
#define NAL_UNIT_TYPE_SPS_EXT 13 // Sequence parameter set extension// 14..18 // Reserved
#define NAL_UNIT_TYPE_CODED_SLICE_AUX 19 // Coded slice of an auxiliary coded picture without partitioning// 20..23 // Reserved// 24..31 // Unspecified2.2 RTP分片传输
如果需要将pkt分成若干个片段进行传输需要在RTP Header之后增加两个标识字段分别是FU IndicatorFragment Unit和FU Header其位置表示为
// 使用最大的RTP Payload情况即RTP Payload1460 Bytes
---------------------------------------------------------------------------
| IP Header | UDP Header | RTP Header | FU Indicator | FU Header | RTP Payload |
---------------------------------------------------------------------------
| 20 Bytes | 8 Bytes | 12 Bytes | 1 Bytes | 1 Bytes | payload_size |
---------------------------------------------------------------------------FU Indicator的字段为
// FU Indicator
---------------
|0|1|2|3|4|5|6|7|
---------------
|F|NRI| Type |
---------------其中F和NRI与前面的一样 1Fforbidden_zero_bit默认为0 2NRI 表示NALU的重要程度越大则越重要最大为3 3TypeNALU的类型如果是H264格式则为28表示H264的第一个分片 FU Header的字段为
// FU Header
---------------
|0|1|2|3|4|5|6|7|
---------------
|S|E|R| Type |
---------------其中 1S如果为1则标识为第一个分片否则不是第一个分片 2E如果为1则表示为最后一个分片否则不是最后一个分片 3R保留位必须为0 4TypeNALU的类型如果是H264格式就是H264的NALU类型
2.3 RTP多片合入传输
这种情况比较少见暂时不做记录
3.工程
基于前面对于RTP协议的理解写一个简易的发送器和接收器执行的大约流程是 1发送端将本地已有的h264码流文件按照RTP格式进行打包推送到本机地址127.0.0.1端口号为8880 2接收端接收传输过来的数据包进行解析并且存储要求可以正常进行解析播放功能后续再做 工程中的代码结构为 发送端的核心函数是udp_send_packet()其中调用了rtp_send_packet最后会调用Winsock函数sendto将数据包传输到对应的IP和端口接收端的核心函数是recvfrom()和check_fragment()recvfrom获取远端传输过来的数据包check_fragment()检查获取到的数据包是否是分片的如果是分片的还会进行拼接。在接收端通过控制宏来决定是否要存储传输过来的数据包
3.1 头文件
3.1.1 rtp.h
rtp.h定义了RTP协议的Headerpacket和上下文
#pragma once
#include stdio.h
#include stdint.h
#include WinSock2.hextern C
{
#include libavcodec/avcodec.h
#include libavformat/avformat.h
#include libswscale/swscale.h
#include libavutil/imgutils.h
};#define RECV_DATA_SIZE 10000
#define MAX_BUFF_SIZE 32 * 1024 * 1024#define RTP_MAX_PKT_SIZE 1400 // RTP数据包最大尺寸一般1400左右
#define RTP_HEADER_SIZE 12
#define RTP_PADDING_SIZE 64#define RTP_PACKET_START 1
#define RTP_PACKET_FRAGMENT 2
#define RTP_PACKET_END 3#define STREAM_DOWNLOAD 0
#define YUV_DOWNLOAD 0// 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
//--------------------------------
//|V2|P|X| CC |M| PT | sequence number |
//--------------------------------
//| timestamp |
//--------------------------------
//| synchronization source(SSRC) identifier |
//
//| contributing source(CSRC) identifiers |
//| .... |
//--------------------------------
typedef struct rtp_header
{// 存储时高位存储的是version/* byte 0 */uint8_t csrc_len : 4; /* expect 0 */uint8_t extension : 1; /* expect 1 */uint8_t padding : 1; /* expect 0 */uint8_t version : 2; /* expect 2 *//* byte 1 */uint8_t payload_type : 7;uint8_t marker : 1; /* expect 1 *//* bytes 2, 3 */uint16_t seq_num;/* bytes 4-7 */uint32_t timestamp;/* bytes 8-11 */uint32_t ssrc; /* stream number is used here. */
}rtp_header_t;typedef struct rtp_packet
{rtp_header_t rtp_h;uint8_t rtp_data[RTP_MAX_PKT_SIZE RTP_PADDING_SIZE];
}rtp_packet_t;typedef struct rtp_context
{int rtp_packet_cnt; // 一共接收到多少个packetint rtp_buffer_size; // 当前buffer中的sizeint rtp_frame_cnt; // 这些packet一共是多少帧int packet_loc; // 当前packet所在位置是否为一帧的起始packet或者最终packetuint8_t* rtp_buffer_data; // 这个data会将之前接收到的数据存储起来
}rtp_context_t;int udp_parser(const char* in_url, int port);3.1.2 utils.h
utils.h中定义了一些辅助性检查的工具以及一个用于分开NALU的函数find_nal_unit
#pragma once
#include rtp.hint find_nal_unit(uint8_t* buf, int size, int* nal_start, int* nal_end);void debug_byte(uint8_t* p, int size);
void debug_rtp_header(rtp_header_t* rtp_h);
void debug_rtp(rtp_packet_t* rtp_pkt, int pkt_size);
void debug_fragment_header(uint8_t* data, int size);3.2 cpp文件
3.2.1 rtp.cpp
rtp.cpp文件中定义了RTP协议的实现方式包括发送和接收实现细节用注释表明代码结构上可能还有不足的地方不过功能正常
#pragma warning(disable : 4996)
#pragma comment(lib, ws2_32.lib) #include ./include/rtp.h
#include ./include/parse.h
#include ./include/utils.hFILE* fp_in;void set_default_rtp_context(rtp_context_t* rtp_ctx)
{memset(rtp_ctx-rtp_buffer_data, 0, sizeof(rtp_ctx-rtp_buffer_size));rtp_ctx-rtp_packet_cnt 0;rtp_ctx-rtp_buffer_size 0;rtp_ctx-packet_loc 0;
}int check_nalu_header(uint8_t data0)
{int forbidden_zero_bit data0 0x80; // 1bitint nal_ref_idc data0 0x60; // 2 bitint nal_unit_type data0 0x1F; // 5bitif ((data0 0x80) 1){printf(forbidden zero bit should be 0\n);return -1;}return nal_unit_type;
}int check_fragment_nalu_header(rtp_context_t* rtp_ctx, uint8_t data0, uint8_t data1)
{int nal_unit_type check_nalu_header(data0);int s, e, type;int pos;if (nal_unit_type 28) // H264{s data1 0x80; // Se data1 0x40; // Etype data1 0x1F; // typepos data1 0xC0; // 1100 0000switch (pos){case 0x80:rtp_ctx-packet_loc RTP_PACKET_START;break;case 0x40:rtp_ctx-packet_loc RTP_PACKET_END;break;case 0x00:rtp_ctx-packet_loc RTP_PACKET_FRAGMENT;break;default: // errorprintf(invalid packet loc\n);return -1;break;}}return 0;
}// Check the data is fragment or not, if fragment, try to concate
int check_fragment(rtp_context_t* rtp_ctx, rtp_packet_t* rtp_pkt, uint8_t* data, int size)
{int nal_start, nal_end;int ret 0;int data_size size - RTP_HEADER_SIZE;find_nal_unit(data, data_size, nal_start, nal_end); // check NALU split posuint8_t data0 data[nal_start];uint8_t data1 data[nal_start 1];uint8_t fu_indicator, fu_header;if (nal_start 0 nal_start 5) // single-fragment, maybe SPS, PPS or small size frame{fu_indicator 0;fu_header 0;ret check_nalu_header(data0); // update nalu_typertp_ctx-rtp_buffer_data (uint8_t*)realloc(rtp_ctx-rtp_buffer_data, (rtp_ctx-rtp_buffer_size data_size) * sizeof(uint8_t));memcpy(rtp_ctx-rtp_buffer_data rtp_ctx-rtp_buffer_size, data, data_size);#if STREAM_DOWNLOADfwrite(rtp_ctx-rtp_buffer_data rtp_ctx-rtp_buffer_size, 1, data_size, fp_in);
#endiffprintf(stdout, rtp_ctx frame cnt:%d, frame_size:%d\n, rtp_ctx-rtp_frame_cnt, data_size);rtp_ctx-rtp_frame_cnt;rtp_ctx-rtp_buffer_size data_size;}else // multi-fragment{fu_indicator data[0];fu_header data[1];ret check_fragment_nalu_header(rtp_ctx, fu_indicator, fu_header);if (ret 0){printf(invalid nalu header\n);return -1;}int real_data_size data_size - 2;rtp_ctx-rtp_buffer_data (uint8_t*)realloc(rtp_ctx-rtp_buffer_data, (rtp_ctx-rtp_buffer_size real_data_size) * sizeof(uint8_t));if (!rtp_ctx-rtp_buffer_data){printf(realloc rtp_buffer_data failed\n);return -1;}memcpy(rtp_ctx-rtp_buffer_data rtp_ctx-rtp_buffer_size, data 2, real_data_size); // plus 2 to skip fu_indicator and fu_header
#if STREAM_DOWNLOADfwrite(rtp_ctx-rtp_buffer_data rtp_ctx-rtp_buffer_size, 1, real_data_size, fp_in);fflush(fp_in);
#endifrtp_ctx-rtp_packet_cnt;rtp_ctx-rtp_buffer_size real_data_size;if (rtp_ctx-packet_loc RTP_PACKET_END) // end of packet{fprintf(stdout, rtp_ctx frame cnt:%d, frame_size:%d\n, rtp_ctx-rtp_frame_cnt, rtp_ctx-rtp_buffer_size);rtp_ctx-rtp_frame_cnt;}}return 0;
}int udp_parser(const char* in_url, int port)
{WSADATA wsaData;// 指定应用程序希望使用的Windows sockets规范版本最高有效字节指定了主版本号最低有效字节指定了次版本号// 如果是 MAKEWORD(2, 2)则对应于 Winsock 2.2WORD sockVersion MAKEWORD(2, 2);int cnt 0;// 初始化Windows Sockets DLL// wsaData用于接收关于Winsock DLL的详细信息包括实际的Windows Sockets版本号if (WSAStartup(sockVersion, wsaData) ! 0){return 0;}// 创建套接字就像是网络通信的一条通道// 类似于对普通文件的fopen操作这个SOCKET描述符唯一标识了一个socket后续的网络操作都围绕着这个socket描述符进行SOCKET ser_socket socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (ser_socket INVALID_SOCKET){ERROR(Invalid socket);return -1;}int on 1;setsockopt(ser_socket, SOL_SOCKET, SO_REUSEADDR, (const char*) on, sizeof(on));sockaddr_in ser_addr;// 地址簇标识符用于指明地址的类型以便系统能够正确地处理该地址ser_addr.sin_family AF_INET;// 表示端口号16位无符号整数端口号在网络中以网络字节序big-endian存储// 通常需要使用htons()函数即Host to Network Short将其从主机字节序转换为网络字节序ser_addr.sin_port htons(port);// sin_addr之中存储IPv4地址32位无符号整数按照网络字节序存储通常会使用inet_addr()函数将// 点分十进制的IP地址字符串9如192.168.1.1转换为这个整数// ser_addr.sin_addr.S_un.S_addr INADDR_ANY;ser_addr.sin_addr.s_addr inet_addr(in_url);// 将一个本地地址包括主机地址和端口号与一个未连接的socket相关联从而建立起套接字的本地连接if (bind(ser_socket, (sockaddr*) ser_addr, sizeof(ser_addr)) SOCKET_ERROR){printf(Bind socket addr error\n);closesocket(ser_socket);return -1;}sockaddr_in remote_addr;int addr_len sizeof(remote_addr);int parse_rtp 1;int parse_mpegts 1;fprintf(stdout, Listening on port:%d\n, port);char recv_data[RECV_DATA_SIZE];rtp_context_t* rtp_ctx (rtp_context_t*)calloc(1, sizeof(rtp_context_t));if (!rtp_ctx){printf(alloc rtp_ctx failed\n);return -1;}rtp_packet_t* rtp_pkt (rtp_packet_t*)calloc(1, sizeof(rtp_packet_t));if (!rtp_pkt){printf(alloc rtp_pkt failed\n);return -1;}int nal_start, nal_end;int data_size 0;while (1){int pkt_size recvfrom(ser_socket, recv_data, RECV_DATA_SIZE, 0, (sockaddr*) remote_addr, addr_len);if (pkt_size 0){if (parse_rtp ! 0){char payload_str[10] { 0 };memcpy(rtp_pkt, recv_data, pkt_size);check_fragment(rtp_ctx, rtp_pkt, rtp_pkt-rtp_data, pkt_size); // check pkt data is fragment or not// RFC3551rtp_header_t rtp_h rtp_pkt-rtp_h;unsigned int timestamp ntohl(rtp_h.timestamp);unsigned int seq_num ntohs(rtp_h.seq_num);char payload rtp_h.payload_type;if (rtp_ctx-packet_loc RTP_PACKET_END) // parse data{switch (payload){case 33: // mpegts// mpegts_packet_parse((uint8_t*)rtp_data, parse_mpegts, payload, rtp_data_size); // TODO: add mpegts parserprintf(MPEGTS type\n);break;case 96: // h264sprintf(payload_str, H264);//h264_packet_parse(rtp_ctx); // TODO : add h264 parse and SDL playbreak;default:printf(Unknown type\n);break;}// printf([RTP PKT] %5d| %5s | %10u| %5d| %5d\n, cnt, payload_str, timestamp, seq_num, pkt_size);set_default_rtp_context(rtp_ctx); // set default rtp ctx value}}cnt;}}free(rtp_ctx-rtp_buffer_data);free(rtp_ctx);free(rtp_pkt);closesocket(ser_socket);WSACleanup();return 0;
}int rtp_send_packet(SOCKET * socket, rtp_packet_t * rtp_pkt, int port, const char* out_url, int size)
{int ret;sockaddr_in ser_addr;ser_addr.sin_family AF_INET;ser_addr.sin_port htons(port);ser_addr.sin_addr.s_addr inet_addr(out_url);rtp_pkt-rtp_h.seq_num htons(rtp_pkt-rtp_h.seq_num);rtp_pkt-rtp_h.timestamp htonl(rtp_pkt-rtp_h.timestamp);rtp_pkt-rtp_h.ssrc htonl(rtp_pkt-rtp_h.ssrc);// send packet to specified IP and portret sendto(*socket, (const char*)rtp_pkt, size, 0, (struct sockaddr*) ser_addr, sizeof(ser_addr));rtp_pkt-rtp_h.seq_num ntohs(rtp_pkt-rtp_h.seq_num);rtp_pkt-rtp_h.timestamp ntohl(rtp_pkt-rtp_h.timestamp);rtp_pkt-rtp_h.ssrc ntohl(rtp_pkt-rtp_h.ssrc);return ret;
}int udp_send_packet(rtp_packet_t * rtp_pkt, uint8_t * buf, int start_pos, int size, SOCKET * socket, int port, const char* out_url)
{int ret 0;int send_bytes 0;int fps 25;int nal_type buf[0];if (size RTP_MAX_PKT_SIZE) // single fragment{memcpy(rtp_pkt-rtp_data, buf, size);ret rtp_send_packet(socket, rtp_pkt, port, out_url, size RTP_HEADER_SIZE);if (ret 0){printf(rtp send packet failed\n);return -1;}send_bytes ret;}else // multi-fragment{/** 0 1 2* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3* ---------------------------* | FU indicator | FU header | FU payload ... |* ---------------------------*//** FU Indicator* 0 1 2 3 4 5 6 7* --------* |F|NRI| Type |* ---------------*//** FU Header* 0 1 2 3 4 5 6 7* --------* |S|E|R| Type |* ---------------*/// 一共需要传输几个数据包int pkt_num size / RTP_MAX_PKT_SIZE; // RTP_MAX_PKT_SIZE1400// 最后一个数据包的大小int pkt_left size % RTP_MAX_PKT_SIZE;int i, pos 0;// 发送完整的包for (int i 0; i pkt_num; i){// 0x60 : 0110 0000, F0, NRI11, Type0000// 28 : Type28H264标准的定义rtp_pkt-rtp_data[0] (nal_type 0x60) | 28;// 0x1F : 0001 1111, S0, E0, R0, Type 11111// 去掉前面3位只保留NALU_TYPErtp_pkt-rtp_data[1] nal_type 0x1F;if (i 0) // 第一个包数据{// 0x80 : 1000 0000, S1, 表示第一个包rtp_pkt-rtp_data[1] | 0x80; // start}else if (pkt_left 0 i pkt_num - 1){// 0x40 : 0100 0000, S0, E1,表示最后一个包rtp_pkt-rtp_data[1] | 0x40; // end}rtp_pkt-rtp_data[1] | buf[start_pos] 0x1F; // NALU type// 从第三个字节开始将数据填充到rtp_data中填充1400个字节memcpy(rtp_pkt-rtp_data 2, buf pos, RTP_MAX_PKT_SIZE);// 发送数据包一共是1400data 2FU Indicator FU Header 12RTP Header 1414ret rtp_send_packet(socket, rtp_pkt, port, out_url, RTP_MAX_PKT_SIZE 2 RTP_HEADER_SIZE);if (ret 0){printf(rtp send packet failed\n);return -1;}send_bytes ret;rtp_pkt-rtp_h.seq_num;pos RTP_MAX_PKT_SIZE;}// 发送最后剩余的数据if (pkt_left 0){rtp_pkt-rtp_data[0] (nal_type 0x60) | 28;rtp_pkt-rtp_data[1] nal_type 0x1F;rtp_pkt-rtp_data[1] | 0x40; // endrtp_pkt-rtp_data[1] | buf[start_pos] 0x1F; // NALU typememcpy(rtp_pkt-rtp_data 2, buf pos, pkt_left);ret rtp_send_packet(socket, rtp_pkt, port, out_url, pkt_left 2 RTP_HEADER_SIZE);if (ret 0){printf(rtp send packet failed\n);return -1;}send_bytes ret;rtp_pkt-rtp_h.seq_num;}}rtp_pkt-rtp_h.timestamp 90000 / fps;return send_bytes;
}int udp_send(const char* output_url, const char* output_file, int port)
{FILE* out_file fopen(output_file, rb);WSADATA wsaData;WORD sockVersion MAKEWORD(2, 2);if (WSAStartup(sockVersion, wsaData) ! 0){return 0;}SOCKET ser_socket socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // UDP protocolif (ser_socket INVALID_SOCKET){printf(Invalid socket\n);return -1;}int on 1;setsockopt(ser_socket, SOL_SOCKET, SO_REUSEADDR, (const char*) on, sizeof(on));rtp_packet_t* rtp_pkt (rtp_packet_t*)malloc(sizeof(rtp_packet_t));if (!rtp_pkt){printf(calloc rtp pkt failed\n);return -1;}memset(rtp_pkt, 0, sizeof(rtp_pkt));rtp_pkt-rtp_h.payload_type 96; // H264rtp_pkt-rtp_h.version 2;rtp_pkt-rtp_h.ssrc 0x88923423;int frame_size 0;int ret 0;int send_bytes 0;int nal_type 0;int fps 25;static int cnt 0;uint8_t* buf (uint8_t*)malloc(MAX_BUFF_SIZE);if (!buf){printf(malloc buf failed\n);return -1;}size_t rsz 0;size_t sz 0;int64_t off 0;uint8_t* p buf;size_t send_byte_total 0;size_t read_byte_total 0;int nal_start 0;int nal_end 0;while (1){rsz fread(buf sz, 1, MAX_BUFF_SIZE - sz, out_file);read_byte_total rsz;if (rsz 0){if (ferror(out_file)) { fprintf(stderr, !! Error: read failed: %s \n, strerror(errno)); break; }printf(end of file, flush buffer if necessary \n);int diff read_byte_total - send_byte_total;if (diff 0 diff nal_end) // flush remaining data{ret udp_send_packet(rtp_pkt, p, nal_start, nal_end, ser_socket, port, output_url);printf(flush remaining data, data size:%d, send_bytes:%d, nal_end, ret);}break; // if (feof(infile)) }sz rsz;while (find_nal_unit(p, sz, nal_start, nal_end) 0) // find nal unit pos{// send udp packetret udp_send_packet(rtp_pkt, p, nal_start, nal_end, ser_socket, port, output_url);printf(start_size:%d, pkt_num:%d, size%d, send_bytes%d\n, nal_start, cnt, nal_end, ret);if (ret 0){printf(send pkt failed\n);break;}p nal_end;sz - nal_end;send_byte_total nal_end;Sleep(1000 / fps);}// if no NALs found in buffer, discard itif (p buf){fprintf(stderr, !! Did not find any NALs between offset %lld (0x%04llX), size %lld (0x%04llX), discarding \n,(long long int)off,(long long int)off,(long long int)off sz,(long long int)off sz);p buf sz;sz 0;}memmove(buf, p, sz);off p - buf;p buf;}free(rtp_pkt);fclose(out_file);free(p);closesocket(ser_socket);WSACleanup();return 0;
}int main()
{
#if STREAM_DOWNLOADfp_in fopen(rtp_receive.h264, wb);
#endif// 1.parse udp dataudp_parser(127.0.0.1, 8880);// 2.send udp data//udp_send(127.0.0.1, output.h264, 8880);#if STREAM_DOWNLOADfclose(fp_in);
#endifreturn 0;
}3.2.2 utils.cpp
utils.cpp中定义了一些用于检查数据的工具比较简单
#include utils.hvoid debug_byte(uint8_t* p, int size)
{int i;for (i 0; i size; i){printf(data[%d]%x , i, p[i]);if (i ! 0 (i % 10 0 || i size - 1)){printf(\n);}}printf(\n);
}void debug_rtp_header(rtp_header_t* rtp_h)
{printf(rtp_header-version:%d\n, rtp_h-version);printf(rtp_header-padding:%d\n, rtp_h-padding);printf(rtp_header-extension:%d\n, rtp_h-extension);printf(rtp_header-csrc_len:%d\n, rtp_h-csrc_len);printf(rtp_header-payload_type:%d\n, rtp_h-payload_type);printf(rtp_header-marker:%d\n, rtp_h-marker);printf(rtp_header-seq_num:%d\n, rtp_h-seq_num);printf(rtp_header-timestamp:%d\n, rtp_h-timestamp);printf(rtp_header-ssrc:%x\n, rtp_h-ssrc);
}void debug_rtp(rtp_packet_t* rtp_pkt, int pkt_size)
{debug_rtp_header(rtp_pkt-rtp_h);debug_byte(rtp_pkt-rtp_data, pkt_size);
}int find_nal_unit(uint8_t* buf, int size, int* nal_start, int* nal_end)
{int i;// find start*nal_start 0;*nal_end 0;i 0;while ( //( next_bits( 24 ) ! 0x000001 next_bits( 32 ) ! 0x00000001 )(buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0x01) (buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0 || buf[i 3] ! 0x01)){i; // skip leading zeroif (i 4 size) { return 0; } // did not find nal start}if (buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0x01) // ( next_bits( 24 ) ! 0x000001 ){i;}if (buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0x01) { /* error, should never happen */ return 0; }i 3;*nal_start i;while ( //( next_bits( 24 ) ! 0x000000 next_bits( 24 ) ! 0x000001 )(buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0) (buf[i] ! 0 || buf[i 1] ! 0 || buf[i 2] ! 0x01)){i;// FIXME the next line fails when reading a nal that ends exactly at the end of the dataif (i 3 size) { *nal_end size; return -1; } // did not find nal end, stream ended first}*nal_end i;return (*nal_end - *nal_start);
}4.测试
传输使用的文件为output.h264这是一个Crew_1280x720.yuv编码而来的码流文件一共有600帧 在使用的时候可以先将发送端打包成exe在cmd中执行send.exe然后进行码流的接收receive.exe这样就可以实现测试
发送端 由于find_nal_unit无法分析出来最后一帧的nal_start和nal_end所以还需要额外flush一次 接收端 也可以用码流分析器正常打开粗略的对比了一下没有发现有误差并且ffplay也可以正常播放
5.小结
在做这样一些工作的时候其实还思考了如何从摄像头获取数据送入到发送端以及如何在解码端进行解码和SDL播放不过这里的代码结构修改起来比较麻烦后续会继续做解码和SDL播放的工作。另外在这些处理这些数据的时候需要进行良好的内存管理否则非常容易出现内存崩溃的问题这一点应该多参考FFmpeg当中的内存管理机制尤其是AVRefBuf这个变量的定义比较重要
CSDN : https://blog.csdn.net/weixin_42877471 Github : https://github.com/DoFulangChen