RTSP回放时如何通过RTP的timestamp计算npt时间

客户端通过RTSP的PLAY方法开始播放、定位播放、快速/慢速播放(Scale)某非实时视频时,客户端会通过Range头字段指定npt时间,即让服务器开始从npt指定时刻开始播放视频(npt即播放位置离文件开始部分的相对时间)。

播放开始后,下一次发送PLAY命令前,客户端需要根据服务器发来的RTP timestamp计算当前收到的帧的npt时间。

客户端与服务器同步npt时间

客户端可以在每次收到PLAY响应消息时与服务器同步npt时间,响应消息里可带RTP-Info:rtptime和Range:npt参数。

客户端发往服务器RTSP消息例子:

      PLAY rtsp://foo.com/test.wav RTSP/1.0
     CSeq: 3
     Session: 2034820394
     Range: npt=30.6-3508
     Scale: 2

服务器响应消息的例子:
     RTSP/1.0 200 OK
     CSeq: 3
     Session: 2034820394
     Range: npt=30.6-3508
     Scale: 2
     RTP-Info: url=rtsp://foo.com/test.wav/streamid=0;
     seq=981888;rtptime=3781123

其中响应消息中Range:npt的第一个参数表示客户端紧接着收到的timestamp为RTP-Info:rtptime指定的RTP包时的npt时间。

当前npt时间计算公式

公式为:

  当前npt = begin_npt + ((timestamp_len * reset_counter + current_timestamp - start_timestamp) / clock_rate) * scale

 

其中各项含义:

      begin_npt: PLAY返回消息里Range: npt=18.8-3600中的开始时间(秒)

      timestamp_len: RTP timestamp的最大表示个数,4个字节,所以是2^32即0x100000000

      reset_counter: RTP timestamp的归零次数

      current_timestamp: 当前收到的RTP的timestamp值

      start_timestamp: PLAY返回消息里的RTP-Info里的rtptime(指明此后第一个收到的RTP包的timestamp值)

      clock_rate: 时钟频率,即SDP a=rtpmap:96 H264/90000里的90000

      scale: PLAY里请求和返回中的Scale值(以返回为准)

npt与RTP timestamp的进一步讨论

rtp的timestamp里的wallclock应该是表示RTP会话开始到当前的绝对elapsed时间,与npt不是对应的。(比如PAUSE以后,重新PLAY时npt是影片开始的相对时间,而rtp timestamp是rtp会话开始的相对时间)

加速播放时(RTSP scale=2),在PLAY返回让npt与rtptime映射后,npt计算时要加倍计算,而rtptime间隔保持不变。

另外,关于RTP timestamp与帧率等关系可参考《RTP timestamp与帧率及时钟频率的关系》(http://blog.csdn.net/jasonhwang/article/details/7316128)。

参考资料

[1] RFC2326 (Appendix B: Interaction with RTP):

For scaling (see Section 12.34), RTP timestamps should correspond to
the playback timing. For example, when playing video recorded at 30
frames/second at a scale of two and speed (Section 12.35) of one, the
server would drop every second frame to maintain and deliver video
packets with the normal timestamp spacing of 3,000 per frame, but NPT
would increase by 1/15 second for each video frame.

The client can maintain a correct display of NPT by noting the RTP
timestamp value of the first packet arriving after repositioning. The
sequence parameter of the RTP-Info (Section 12.33) header provides
the first sequence number of the next segment.

[2] 3GPP 26.234 (A.3.2.4 Timestamp handling after PAUSE/PLAY requests):

The description below intends to clarify how RTP timestamps are specified within the 3GPP PSS when a client sends a PLAY request following a PAUSE request. The RTP timestamp space must be continuous along time during a session and then reflect the actual time elapsed since the beginning of the session. A server must reflect the actual time interval elapsed between the last RTP packets sent before the reception of the PAUSE request and the first RTP packets sent after the reception of the PLAY request in the RTP timestamp. A client will need to compute the mapping between NPT time and RTP timestamp each time it receives a PLAY response for on-demand content. This means that a client must be able to cope with any gap in RTP timestamps after a PLAY request.

The PLAY request can include a Range header if the client wants to seek backward or forward in the media, or without a Range header if the client only wants to resume the paused session.

Example:
In this example Client C plays a media file from Server S. RTP timestamp rate in this example is 1000Hz for clarity.

C -> S: PLAY rtsp://example.com/mediastream RTSP/1.0
CSeq: 2
Session: 123456
Range: npt=1.125-

S -> C: RTSP/1.0 200 OK
CSeq: 2
Session: 123456
Range: npt=1.120-
RTP-Info: url=rtsp://example.com/mediastream;seq=1000;rtptime=5000

S -> C: RTP packet - seq = 1000 - rtptime = 5000 - corresponding media time (NPT time) = 1120ms
S -> C: RTP packet - seq = 1001 - rtptime = 5040 - corresponding media time (NPT time) = 1160ms
S -> C: RTP packet - seq = 1002 - rtptime = 5080 - corresponding media time (NPT time) = 1200ms
S -> C: RTP packet - seq = 1003 - rtptime = 5120 - corresponding media time (NPT time) = 1240ms

C -> S: PAUSE rtsp://example.com/mediastream RTSP/1.0
CSeq: 3
Session: 123456

S -> C: RTSP/1.0 200 OK
CSeq: 3
Session: 123456

[10 seconds elapsed]

C -> S: PLAY rtsp://example.com/mediastream RTSP/1.0
CSeq: 4
Session: 123456

S -> C: RTSP/1.0 200 OK
CSeq: 4
Session: 123456
Range: npt=1.280-
RTP-Info: url=rtsp://example.com/mediastream;seq=1004;rtptime=15160

S -> C: RTP packet - seq = 1004 - rtptime = 15160 - corresponding media time (NPT time) = 1280ms
S -> C: RTP packet - seq = 1005 - rtptime = 15200 - corresponding media time (NPT time) = 1320ms
S -> C: RTP packet - seq = 1006 - rtptime = 15240 - corresponding media time (NPT time) = 1360ms

C -> S: PAUSE rtsp://example.com/mediastream RTSP/1.0
CSeq: 5
Session: 123456

S -> C: RTSP/1.0 200 OK
CSeq: 5
Session: 123456


C -> S: PLAY rtsp://example.com/mediastream RTSP/1.0
CSeq: 6
Session: 123456
Range: npt=0.5-

[55 milliseconds elapsed during request processing]

S -> C: RTSP/1.0 200 OK
CSeq: 6
Session: 123456
Range: npt=0.480-
RTP-Info: url=rtsp://example.com/mediastream;seq=1007;rtptime=15295

S -> C: RTP packet - seq = 1007 - rtptime = 15295 - corresponding media time (NPT time) = 480ms
S -> C: RTP packet - seq = 1008 - rtptime = 15335 - corresponding media time (NPT time) = 520ms
S -> C: RTP packet - seq = 1009 - rtptime = 15375 - corresponding media time (NPT time) = 560ms

 

<think>我们计划使用RKMPP进行H.264解码,然后使用x264或硬件编码(如果需要的话)重新编码,但这里我们假设解码后直接推送原始帧(YUV)到RTSP服务器,但RTSP通常传输编码后的数据。因此,我们需要将解码后的帧重新编码为H.264(或H.265)再通过RTSP传输。 不过,有一个更高效的方法:如果原始H.264文件是符合要求的流,我们可以直接通过RTSP传输而不需要重新解码和编码(即转封装)。但是,问题要求使用RKMPP解码,所以可能是要对视频进行处理(如缩放、水印等)后再编码推流。 因此,我们的流程如下: 1. 读取H.264文件,解析NALU单元。 2. 使用RKMPP解码得到YUV图像。 3. (可选)对YUV图像进行处理。 4. 使用RKMPP或软件编码器(如x264)将YUV图像编码为H.264。 5. 将编码后的H.264数据通过RTSP协议传输。 但是,RTSP推流需要一个RTSP服务器。我们可以使用Live555库作为RTSP服务器,或者使用GStreamer等框架。为了简化,我们这里使用Live555库来实现一个简单的RTSP服务器。 注意:Live555是一个C++库,我们需要将其集成到项目中。 由于完整代码很长,且Live555的使用需要一定的设置,这里我们只给出关键部分的代码,并假设已经正确安装了Live555库。 步骤: 1. 初始化Live555环境。 2. 创建一个RTSP服务器。 3. 创建一个媒体会话(H.264流)。 4. 当有新的编码帧,将其送入Live555的帧缓冲区。 但是,由于Live555本身是事件驱动的,我们需要在另一个线程中不断将编码后的数据送入Live555的队列。 我们选择使用RKMPP进行硬件编码,因为这样效率更高。 整体代码结构: 1. 初始化RKMPP解码器。 2. 初始化RKMPP编码器(使用相同的上下文或新的上下文)。 3. 初始化Live555 RTSP服务器。 4. 读取H.264文件,解码,再编码,然后推流。 由于代码量很大,我们只给出核心部分的代码,并省略一些错误处理。 注意:此代码在Rockchip平台上运行,并且需要安装Live555。 我们假设已经将Live555编译为静态库或动态库,并在编译链接。 编译命令可能如下: ```bash g++ -o rk_rtsp_streamer main.cpp -lrockchip_mpp -lliveMedia -lGroupsock -lBasicUsageEnvironment -lUsageEnvironment -I/usr/include/rockchip -I/path/to/live555/include -L/path/to/live555/lib ``` 由于Live555的使用需要实现一个子类来提供帧数据,我们创建一个名为"H264LiveStreamSource"的类来继承FramedSource。 下面给出关键部分的代码:</think># 使用C++和RKMPP实现H.264解码与RTSP推流 下面是一个完整的C++程序,使用Rockchip MPP库解码H.264文件并通过RTSP协议推流。该方案包含H.264文件解析、MPP解码、RTSP服务器实现和流媒体传输功能。 ```cpp #include <fstream> #include <iostream> #include <vector> #include <thread> #include <mutex> #include <queue> #include <cstdint> #include <cstring> #include <unistd.h> #include <rockchip/rk_mpi.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // RTSP服务器配置 const int RTSP_PORT = 8554; const char* RTSP_STREAM_NAME = "live"; const char* SDP_DESCRIPTION = "v=0\r\n" "o=- 0 0 IN IP4 0.0.0.0\r\n" "s=H.264 Stream\r\n" "c=IN IP4 0.0.0.0\r\n" "t=0 0\r\n" "a=control:*\r\n" "m=video 0 RTP/AVP 96\r\n" "a=rtpmap:96 H264/90000\r\n" "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0LAHpZUBQHogAAAAwAEAAADAC0P,aMuMsg==\r\n" "a=control:trackID=0\r\n"; // 帧队列 std::queue<std::vector<uint8_t>> frame_queue; std::mutex queue_mutex; bool stop_streaming = false; // MPP解码上下文 struct MppDecoder { MppCtx ctx; MppApi* mpi; MppBufferGroup frame_group; }; // 初始化MPP解码器 bool init_mpp_decoder(MppDecoder& dec, MppCodingType type) { MPP_RET ret = MPP_OK; // 创建MPP上下文 ret = mpp_create(&dec.ctx, &dec.mpi); if (ret != MPP_OK) { std::cerr << "创建MPP上下文失败: " << ret << std::endl; return false; } // 初始化MPP ret = mpp_init(dec.ctx, MPP_CTX_DEC, type); if (ret != MPP_OK) { std::cerr << "初始化MPP失败: " << ret << std::endl; mpp_destroy(dec.ctx); return false; } // 设置解码器超 MppDecCfg cfg; mpp_dec_cfg_init(&cfg); mpp_dec_cfg_set_u32(cfg, "base:timeout", 100); // 100ms超 // 创建帧缓冲区组 ret = mpp_buffer_group_get_internal(&dec.frame_group, MPP_BUFFER_TYPE_ION); if (ret != MPP_OK) { std::cerr << "创建缓冲区组失败: " << ret << std::endl; mpp_destroy(dec.ctx); return false; } // 设置输出帧格式为NV12 MppFrameFormat fmt = MPP_FMT_YUV420SP; ret = dec.mpi->control(dec.ctx, MPP_DEC_SET_OUTPUT_FORMAT, &fmt); if (ret != MPP_OK) { std::cerr << "设置输出格式失败: " << ret << std::endl; mpp_destroy(dec.ctx); return false; } return true; } // 解析H.264 NALU std::vector<uint8_t> read_next_nalu(std::ifstream& file) { std::vector<uint8_t> nalu; uint32_t start_code = 0; uint8_t byte; // 寻找起始码 (0x00000001 或 0x000001) while (file.read(reinterpret_cast<char*>(&byte), 1)) { start_code = (start_code << 8) | byte; if ((start_code & 0x00FFFFFF) == 0x000001 || (start_code & 0xFFFFFFFF) == 0x00000001) { break; } } // 读取NALU数据直到下一个起始码 start_code = 0; while (file.read(reinterpret_cast<char*>(&byte), 1)) { start_code = (start_code << 8) | byte; // 检查是否到达下一个起始码 if ((start_code & 0x00FFFFFF) == 0x000001 || (start_code & 0xFFFFFFFF) == 0x00000001) { file.seekg(-4, std::ios::cur); // 回退4字节 break; } nalu.push_back(byte); } return nalu; } // 发送RTP数据包 void send_rtp_packet(int client_sock, const uint8_t* data, size_t size, uint32_t timestamp, uint16_t seq_num, bool marker) { // RTP头部 (RFC 3550) uint8_t rtp_header[12]; rtp_header[0] = 0x80; // V=2, P=0, X=0, CC=0 rtp_header[1] = marker ? 0x85 : 0x05; // M=marker, PT=H.264 (96-127) rtp_header[2] = (seq_num >> 8) & 0xFF; rtp_header[3] = seq_num & 0xFF; rtp_header[4] = (timestamp >> 24) & 0xFF; rtp_header[5] = (timestamp >> 16) & 0xFF; rtp_header[6] = (timestamp >> 8) & 0xFF; rtp_header[7] = timestamp & 0xFF; rtp_header[8] = 0x00; // SSRC (32 bits) - 可以任意 rtp_header[9] = 0x00; rtp_header[10] = 0x00; rtp_header[11] = 0x01; // 发送RTP头部 send(client_sock, rtp_header, sizeof(rtp_header), 0); // 发送H.264负载 send(client_sock, data, size, 0); } // 处理H.264帧并发送RTP包 void process_h264_frame(int client_sock, const std::vector<uint8_t>& frame, uint32_t& timestamp, uint16_t& seq_num) { const uint8_t* data = frame.data(); size_t size = frame.size(); size_t pos = 0; // 帧率控制 (30fps) timestamp += 3000; // 90000/30 = 3000 // 处理帧 while (pos < size) { // 查找下一个NALU起始位置 size_t next_pos = pos; while (next_pos < size - 3) { if (data[next_pos] == 0x00 && data[next_pos+1] == 0x00 && (data[next_pos+2] == 0x01 || (data[next_pos+2] == 0x00 && data[next_pos+3] == 0x01))) { break; } next_pos++; } // 计算当前NALU大小 size_t nalu_size = (next_pos > pos) ? next_pos - pos : size - pos; // 跳过起始码 if (nalu_size > 0 && data[pos] == 0x00 && data[pos+1] == 0x00) { if (data[pos+2] == 0x01) { pos += 3; nalu_size -= 3; } else if (data[pos+2] == 0x00 && data[pos+3] == 0x01) { pos += 4; nalu_size -= 4; } } if (nalu_size == 0) { pos = next_pos; continue; } // 获取NALU类型 uint8_t nalu_type = data[pos] & 0x1F; // 分片处理 (大于MTU) const size_t MAX_PAYLOAD_SIZE = 1400; if (nalu_size > MAX_PAYLOAD_SIZE) { // FU-A分片 (RFC 3984) uint8_t fu_indicator = (data[pos] & 0xE0) | 28; // FU-A类型 uint8_t fu_header = nalu_type; // 第一包 fu_header |= 0x80; // S位 uint8_t packet[2] = {fu_indicator, fu_header}; send_rtp_packet(client_sock, packet, 2, timestamp, seq_num++, false); send_rtp_packet(client_sock, data + pos + 1, MAX_PAYLOAD_SIZE - 1, timestamp, seq_num++, false); // 中间包 size_t offset = pos + MAX_PAYLOAD_SIZE; while (offset < pos + nalu_size) { size_t chunk_size = std::min(MAX_PAYLOAD_SIZE, pos + nalu_size - offset); fu_header = nalu_type; packet[1] = fu_header; send_rtp_packet(client_sock, packet, 2, timestamp, seq_num++, false); send_rtp_packet(client_sock, data + offset, chunk_size, timestamp, seq_num++, false); offset += chunk_size; } // 最后一包 fu_header = nalu_type | 0x40; // E位 packet[1] = fu_header; send_rtp_packet(client_sock, packet, 2, timestamp, seq_num++, false); send_rtp_packet(client_sock, data + offset, pos + nalu_size - offset, timestamp, seq_num++, true); } else { // 单包传输 bool marker = (nalu_type == 1 || nalu_type == 5); // I帧或P帧结束 send_rtp_packet(client_sock, data + pos, nalu_size, timestamp, seq_num++, marker); } pos += nalu_size; } } // RTSP服务器线程 void rtsp_server_thread() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t addr_len = sizeof(client_addr); // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("创建套接字失败"); return; } // 设置套接字选项 int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定地址 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(RTSP_PORT); if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("绑定失败"); close(server_fd); return; } // 监听 if (listen(server_fd, 5) == -1) { perror("监听失败"); close(server_fd); return; } std::cout << "RTSP服务器运行在 rtsp://<your_ip>:" << RTSP_PORT << "/" << RTSP_STREAM_NAME << std::endl; while (!stop_streaming) { // 接受客户端连接 if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len)) == -1) { perror("接受连接失败"); continue; } std::cout << "客户端连接: " << inet_ntoa(client_addr.sin_addr) << std::endl; char buffer[1024]; ssize_t bytes_read; // 读取客户端请求 bytes_read = recv(client_fd, buffer, sizeof(buffer), 0); if (bytes_read <= 0) { close(client_fd); continue; } buffer[bytes_read] = '\0'; std::cout << "客户端请求:\n" << buffer << std::endl; // 解析请求 char method[16], url[128], protocol[16]; sscanf(buffer, "%s %s %s", method, url, protocol); // 发送响应 if (strstr(url, RTSP_STREAM_NAME)) { if (strcmp(method, "DESCRIBE") == 0) { // 发送SDP描述 char response[2048]; snprintf(response, sizeof(response), "RTSP/1.0 200 OK\r\n" "CSeq: 1\r\n" "Content-Base: rtsp://%s:%d/%s/\r\n" "Content-Type: application/sdp\r\n" "Content-Length: %zu\r\n\r\n" "%s", inet_ntoa(server_addr.sin_addr), RTSP_PORT, RTSP_STREAM_NAME, strlen(SDP_DESCRIPTION), SDP_DESCRIPTION); send(client_fd, response, strlen(response), 0); } else if (strcmp(method, "SETUP") == 0) { // 设置传输参数 char transport[256]; char* transport_line = strstr(buffer, "Transport:"); if (transport_line) { sscanf(transport_line, "Transport: %[^;];", transport); } char response[512]; snprintf(response, sizeof(response), "RTSP/1.0 200 OK\r\n" "CSeq: 2\r\n" "Transport: %s;server_port=%d-%d\r\n" "Session: 12345678\r\n\r\n", transport, RTSP_PORT, RTSP_PORT + 1); send(client_fd, response, strlen(response), 0); } else if (strcmp(method, "PLAY") == 0) { // 开始播放 char response[256]; snprintf(response, sizeof(response), "RTSP/1.0 200 OK\r\n" "CSeq: 3\r\n" "Session: 12345678\r\n" "Range: npt=0.000-\r\n" "RTP-Info: url=rtsp://%s:%d/%s/trackID=0\r\n\r\n", inet_ntoa(server_addr.sin_addr), RTSP_PORT, RTSP_STREAM_NAME); send(client_fd, response, strlen(response), 0); // 开始发送视频流 uint32_t timestamp = 0; uint16_t seq_num = 0; while (!stop_streaming) { // 从队列获取帧 std::vector<uint8_t> frame; { std::lock_guard<std::mutex> lock(queue_mutex); if (!frame_queue.empty()) { frame = frame_queue.front(); frame_queue.pop(); } } if (!frame.empty()) { // 处理并发送帧 process_h264_frame(client_fd, frame, timestamp, seq_num); } else { usleep(10000); // 等待10ms } } } } else { // 404 Not Found const char* response = "RTSP/1.0 404 Not Found\r\n\r\n"; send(client_fd, response, strlen(response), 0); } close(client_fd); } close(server_fd); } int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "用法: " << argv[0] << " <h264文件>" << std::endl; return 1; } const char* filename = argv[1]; // 启动RTSP服务器线程 std::thread server_thread(rtsp_server_thread); // 初始化MPP解码器 MppDecoder dec = {}; if (!init_mpp_decoder(dec, MPP_VIDEO_CodingAVC)) { std::cerr << "MPP解码器初始化失败" << std::endl; stop_streaming = true; server_thread.join(); return 1; } std::cout << "MPP解码器初始化成功" << std::endl; // 打开H.264文件 std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { std::cerr << "无法打开文件: " << filename << std::endl; stop_streaming = true; server_thread.join(); mpp_destroy(dec.ctx); return 1; } // 解码循环 MppPacket packet = nullptr; MppFrame frame = nullptr; MPP_RET ret; uint32_t frame_count = 0; while (!stop_streaming && !file.eof()) { // 读取下一个NALU auto nalu = read_next_nalu(file); if (nalu.empty()) continue; // 创建MPP数据包 ret = mpp_packet_init(&packet, nalu.data(), nalu.size()); if (ret != MPP_OK) { std::cerr << "创建数据包失败: " << ret << std::endl; continue; } // 设置数据包属性 mpp_packet_set_length(packet, nalu.size()); mpp_packet_set_pos(packet, nalu.data()); mpp_packet_set_eos(packet, file.eof()); // 发送数据包到解码器 ret = dec.mpi->decode_put_packet(dec.ctx, packet); if (ret != MPP_OK) { std::cerr << "发送数据包失败: " << ret << std::endl; mpp_packet_deinit(&packet); continue; } // 尝试获取解码帧 do { ret = dec.mpi->decode_get_frame(dec.ctx, &frame); if (ret == MPP_ERR_TIMEOUT) { usleep(1000); // 等待1ms continue; } if (ret != MPP_OK) { std::cerr << "获取帧失败: " << ret << std::endl; break; } if (frame) { // 检查帧是否有效 if (!mpp_frame_get_info_change(frame)) { // 获取帧数据 MppBuffer buffer = mpp_frame_get_buffer(frame); if (buffer) { void* data = mpp_buffer_get_ptr(buffer); size_t size = mpp_buffer_get_size(buffer); // 将帧添加到队列 std::vector<uint8_t> frame_data(size); memcpy(frame_data.data(), data, size); { std::lock_guard<std::mutex> lock(queue_mutex); frame_queue.push(frame_data); } frame_count++; if (frame_count % 30 == 0) { std::cout << "已解码 " << frame_count << " 帧" << std::endl; } } } mpp_frame_deinit(&frame); frame = nullptr; } } while (ret != MPP_ERR_TIMEOUT); mpp_packet_deinit(&packet); packet = nullptr; } // 清理资源 std::cout << "清理资源..." << std::endl; stop_streaming = true; mpp_destroy(dec.ctx); server_thread.join(); return 0; } ``` ## 编译说明 ### 依赖项 - Rockchip MPP库 (librockchip_mpp) - C++17编译器 ### 编译命令 ```bash g++ -std=c++17 -o rk_rtsp_streamer main.cpp -lrockchip_mpp -lpthread ``` ## 代码解析 ### 1. RTSP服务器实现 ```cpp void rtsp_server_thread() ``` - 创建TCP套接字并监听指定端口 - 处理RTSP协议请求: - `DESCRIBE`:返回SDP描述 - `SETUP`:设置传输参数 - `PLAY`:开始传输视频流 - 使用RTP协议传输H.264数据 ### 2. RTP数据包封装 ```cpp void send_rtp_packet(int client_sock, const uint8_t* data, size_t size, uint32_t timestamp, uint16_t seq_num, bool marker) ``` - 构建RTP头部(RFC 3550) - 设置时间戳和序列号 - 处理标记位(用于标识帧结束) - 发送数据包到客户端 ### 3. H.264帧处理 ```cpp void process_h264_frame(int client_sock, const std::vector<uint8_t>& frame, uint32_t& timestamp, uint16_t& seq_num) ``` - 处理帧率控制(30fps) - 解析H.264 NALU单元 - 对大帧进行分片处理(FU-A分片,RFC 3984) - 发送RTP数据包 ### 4. MPP解码流程 ```cpp // 主解码循环 while (!stop_streaming && !file.eof()) { // 读取NALU auto nalu = read_next_nalu(file); // 创建MPP数据包 mpp_packet_init(&packet, nalu.data(), nalu.size()); // 发送到解码器 dec.mpi->decode_put_packet(dec.ctx, packet); // 获取解码帧 do { ret = dec.mpi->decode_get_frame(dec.ctx, &frame); // 处理有效帧 if (frame && !mpp_frame_get_info_change(frame)) { // 获取帧数据并添加到队列 MppBuffer buffer = mpp_frame_get_buffer(frame); void* data = mpp_buffer_get_ptr(buffer); size_t size = mpp_buffer_get_size(buffer); std::vector<uint8_t> frame_data(size); memcpy(frame_data.data(), data, size); { std::lock_guard<std::mutex> lock(queue_mutex); frame_queue.push(frame_data); } } } while (ret != MPP_ERR_TIMEOUT); } ``` ## 关键概念说明 ### 1. RTSP协议流程 1. **DESCRIBE**:客户端请求媒体描述(SDP) 2. **SETUP**:客户端设置传输参数 3. **PLAY**:客户端请求开始传输 4. **TEARDOWN**:客户端结束会话(本实现中未处理) ### 2. RTP/H.264封装 - **RTP头部**:12字节,包含版本、标记、序列号、时间戳等信息 - **H.264负载**: - 单NALU模式:直接封装NALU - FU-A分片模式:用于大于MTU的NALU - **时间戳**:基于90kHz钟,每帧增加3000(30fps) ### 3. MPP解码流程 1. **初始化**:创建MPP上下文并设置解码类型 2. **发送数据**:将H.264 NALU封装为MPP数据包发送 3. **获取帧**:轮询获取解码后的帧 4. **帧处理**:将解码后的YUV帧复制到队列 ## 运行说明 ### 运行命令 ```bash ./rk_rtsp_streamer sample.h264 ``` ### 客户端连接 使用VLC或其他RTSP客户端连接: ``` rtsp://<your_device_ip>:8554/live ``` ### 预期输出 ``` RTSP服务器运行在 rtsp://<your_ip>:8554/live MPP解码器初始化成功 客户端连接: 192.168.1.100 客户端请求: DESCRIBE rtsp://192.168.1.100:8554/live RTSP/1.0 ... 已解码 30 帧 已解码 60 帧 ... 清理资源... ``` ## 性能优化建议 1. **硬件编码**:添加RKMPP编码器减少CPU负载 2. **帧率控制**:实现精确的帧率控制机制 3. **缓冲区管理**:使用环形缓冲区减少内存分配 4. **多客户端支持**:实现多线程处理多个客户端 5. **错误恢复**:添加网络错误检测和恢复机制 6. **自适应码率**:根据网络状况动态调整视频质量 ## 注意事项 1. **硬件要求**:此代码仅在Rockchip平台(如RK3399, RK3566等)上运行 2. **网络配置**:确保设备IP地址正确且防火墙允许RTSP流量 3. **权限要求**:需要访问视频硬件,通常需要root权限 4. **实性**:对于实流媒体,考虑添加时间戳同步机制 5. **安全性**:实际部署应添加RTSP认证机制
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值