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

【RTSP】客户端(一):RTSP协议实现

概述

RTSP主要功能总结

RTSP本质是一个应用层协议,主要用于控制实时数据的传递,例如音视频流。RTSP的传输方式与HTTP类似,与HTTP不同在于RTSP主要用于控制传输媒体服务器上的流媒体会话。所以其是一个 客户端-服务器模型,客户端需要发送请求给服务器,然后服务器返回响应

主要功能

  • 建立和终止流媒体会话:客户端可以使用RTSP来请求服务器建立或者终止流媒体会话
  • 控制媒体流的播放:客户端实现控制媒体流的播放
  • 获取媒体描述信息:客户端可以请求服务器提供流媒体的描述信息,也就是SDP
  • 协商传输参数

请求格式

Method SP Request-URI SP RTSP-Version CRLF
*(General-Header | Request-Header | Entity-Header) CRLF
CRLF
[ Message-Body ]
  • Method: RTSP 方法,例如 OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN 等 (RFC2326 Section 6, 8, 9, 10, 11, 12)
  • Request-URI: 请求的资源 URI,通常是媒体流的地址
  • RTSP-Version: RTSP 协议版本,例如 RTSP/1.0
  • Headers: 请求头字段,提供请求的附加信息 (RFC2326 Section 5)
  • Message-Body (可选): 消息体,用于携带额外的数据,例如 SETUP 请求中的 Transport 头字段

响应格式

RTSP-Version SP Status-Code SP Reason-Phrase CRLF
*(General-Header | Response-Header | Entity-Header) CRLF
CRLF
[ Message-Body ]
  • RTSP-Version: RTSP 协议版本,例如 RTSP/1.0
  • Status-Code: 三位数字的状态码,指示请求的处理结果 (RFC2326 Section 7)。例如 200 OK, 404 Not Found
  • Reason-Phrase: 状态码的简短文本描述,例如 OK, Not Found
  • Headers: 响应头字段,提供响应的附加信息 (RFC2326 Section 5)
  • Message-Body (可选): 消息体,例如 DESCRIBE 响应中的 SDP 信息

核心功能实现

OPTIONS 请求和响应

请求格式

OPTIONS rtsp://example.com/media RTSP/1.0
CSeq: 1
User-Agent: MyRTSPClient/1.0
  • Method: OPTIONS
  • Request-URI: rtsp://example.com/media
  • RTSP-Version: RTSP/1.0
  • Headers:
    • CSeq: 1
    • User-Agent: MyRTSPClient/1.0

响应格式

RTSP/1.0 200 OK
CSeq: 1
Public: DESCRIBE, SETUP, PLAY, TEARDOWN
  • RTSP-Version: RTSP/1.0
  • Status-Code: 200
  • Reason-Phrase: OK
  • Headers:
    • CSeq: 1
    • Public: DESCRIBE, SETUP, PLAY, TEARDOWN

代码实现

// (请求格式)
// OPTIONS rtsp://example.com/media RTSP/1.0
// CSeq: 1
// User-Agent: MyRTSPClient/1.0
int RtspClient::SendOPTIONS(const char *url){char result[512] = {0};sprintf(result, "OPTIONS %s RTSP/1.0\r\n""CSeq: %d\r\n""User-Agent: %s\r\n""\r\n",url,cseq,USER_AGENT);int ret = send(rtsp_sd_, result, strlen(result), 0);
#ifdef RTSP_DEBUGstd::cout <<  __FILE__ << __LINE__ << std::endl;std::cout <<  result << std::endl;
#endifcseq++;return ret;
}
// (响应格式)
// RTSP/1.0 200 OK
// CSeq: 1
// Public: DESCRIBE, SETUP, PLAY, TEARDOWN
int RtspClient::DecodeOPTIONS(const char *buffer, int len){std::string str = buffer;struct ResponseMessage parsed_message;int used_bytes = ParseRTSPMessage(str, parsed_message);buffer_cmd_used_ += used_bytes;if(parsed_message.code < 0){ // internal errorreturn -1;}rtsp_cmd_stat_ = RTSPCMDSTAT::RTSP_DESCRIBE;return 0;
}

DESCRIBE 请求和响应

请求格式

DESCRIBE rtsp://example.com/media RTSP/1.0
CSeq: 2
Accept: application/sdp
  • Method: DESCRIBE
  • Request-URI: rtsp://example.com/media
  • RTSP-Version: RTSP/1.0
  • Headers:
    • CSeq: 2
    • Accept: application/sdp

响应格式

RTSP/1.0 200 OK
CSeq: 2
Content-Type: application/sdp
Content-Length: 158v=0
o=- 12345 12345 IN IP4 192.168.1.1
s=Media Session
t=0 0
a=recvonly
m=video 49170 RTP/AVP 96
a=rtpmap:96 MP4V-ES/90000
a=control:rtsp://example.com/media/video
  • RTSP-Version: RTSP/1.0
  • Status-Code: 200
  • Reason-Phrase: OK
  • Headers:
    • CSeq: 2
    • Content-Type: application/sdp
    • Content-Length: 158
  • Message-Body: SDP 信息,描述媒体会话

解析响应

该请求响应涉及到鉴权,参考后面章节的详细分析 

// (请求格式)
// DESCRIBE rtsp://example.com/media RTSP/1.0
// CSeq: 2
// Accept: application/sdp
int RtspClient::SendDESCRIBE(const char *url, const char *authorization){char result[512] = {0};sprintf(result, "DESCRIBE %s RTSP/1.0\r\n""CSeq: %d\r\n""User-Agent: %s\r\n""Accept: application/sdp\r\n",url,cseq,USER_AGENT);if(authorization){sprintf(result+strlen(result),"Authorization: %s\r\n",authorization);}sprintf(result+strlen(result),"\r\n");cseq++;int ret = send(rtsp_sd_, result, strlen(result), 0);
#ifdef RTSP_DEBUGstd::cout <<  __FILE__ << __LINE__ << std::endl;std::cout <<  result << std::endl;
#endifreturn ret;
}// (响应格式)
// RTSP/1.0 200 OK
// CSeq: 2
// Content-Type: application/sdp
// Content-Length: 158// v=0
// o=- 12345 12345 IN IP4 192.168.1.1
// s=Media Session
// t=0 0
// a=recvonly
// m=video 49170 RTP/AVP 96
// a=rtpmap:96 MP4V-ES/90000
// a=control:rtsp://example.com/media/video
int RtspClient::DecodeDESCRIBE(const char *url, const char *buffer, int len){std::string str = buffer;struct ResponseMessage parsed_message;int used_bytes = ParseRTSPMessage(str, parsed_message);buffer_cmd_used_ += used_bytes;if(parsed_message.code < 0){ // internal errorreturn -1;}if(parsed_message.code == 401){ // Unauthorizedstd::string authenticate = GetValueByKey(parsed_message.result, "WWW-Authenticate");if(authenticate.empty()){buffer_cmd_used_ -= used_bytes;return 0;}ExtractRealmAndNonce(authenticate, realm_, nonce_);std::string response = GenerateAuthResponse(url_info_.username.c_str(), url_info_.password.c_str(), realm_.c_str(), nonce_.c_str(), url, "DESCRIBE");std::string res = GenerateAuthHeader(url, response);SendDESCRIBE(url, res.c_str());return 0;}else{ // 解析SDP// printf("code:%d\n",parsed_message.code);// printf("stat:%s\n",parsed_message.message.c_str());// printf("sdp:%s\n",parsed_message.sdp.c_str());// for (const auto& kvp : parsed_message.result) {//     std::cout << "Key: " << kvp.first << ", Value: " << kvp.second << std::endl;// }std::string content_len_str = GetValueByKey(parsed_message.result, "Content-Length");if(content_len_str.empty() || !parsed_message.find_payload){buffer_cmd_used_ -= used_bytes;return 0;}int content_len = std::stoi(content_len_str);if((len - used_bytes) < content_len){ buffer_cmd_used_ -= used_bytes;return 0;}parsed_message.sdp = str.substr(used_bytes, content_len);buffer_cmd_used_ += content_len;content_base_ = GetValueByKey(parsed_message.result, "Content-Base");if(content_base_.empty()){content_base_ = url;}if(sdp_ == NULL){sdp_ = new SDPParse(parsed_message.sdp, content_base_);sdp_->Parse();video_url_ = sdp_->GetVideoUrl();audio_url_ = sdp_->GetAudioUrl();}}rtsp_cmd_stat_ = RTSPCMDSTAT::RTSP_STEUP;return 0;
}

SETUP 请求和响应

请求格式

SETUP rtsp://example.com/media/trackID=1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001
  • Method: SETUP
  • Request-URI: rtsp://example.com/media/trackID=1
  • RTSP-Version: RTSP/1.0
  • Headers:
    • CSeq: 3
    • Transport: RTP/AVP;unicast;client_port=8000-8001

解析响应

RTSP/1.0 200 OK
CSeq: 3
Transport: RTP/AVP;unicast;server_port=9000-9001;ssrc=12345678
  • RTSP-Version: RTSP/1.0
  • Status-Code: 200
  • Reason-Phrase: OK
  • Headers:
    • CSeq: 3
    • Transport: RTP/AVP;unicast;server_port=9000-9001;ssrc=12345678

代码实现

// (构建请求)
// SETUP rtsp://example.com/media/trackID=1 RTSP/1.0
// CSeq: 3
// Transport: RTP/AVP;unicast;client_port=8000-8001
int RtspClient::SendSTEUP(const char *url){std::string authenticate;// 1. 生成身份验证头部信息if(!realm_.empty() && !nonce_.empty()){std::string response = GenerateAuthResponse(url_info_.username.c_str(), url_info_.password.c_str(), realm_.c_str(), nonce_.c_str(), url, "SETUP");authenticate = GenerateAuthHeader(url, response);}// 2. 构建请求消息头char result[512] = {0};sprintf(result, "SETUP %s RTSP/1.0\r\n""CSeq: %d\r\n""User-Agent: %s\r\n",url,cseq,USER_AGENT);// 3. 添加身份验证头部信息和会话头部信息if(!authenticate.empty()){sprintf(result+strlen(result),"Authorization: %s\r\n",authenticate.c_str());}if(!session_.empty()){sprintf(result+strlen(result),"Session: %s\r\n",session_.c_str());}// 4. 添加传输协议信息,根据RTP传输协议不同,生成不同的Transport头部信息if(rtp_transport_ == TRANSPORT::RTP_OVER_UDP){if(std::string(url) == video_url_){if(CreateRtpSockets(&rtp_sd_video_, &rtcp_sd_video_, &rtp_port_video_, &rtcp_port_video_) < 0){std::cout << "video CreateRtpSockets error" << std::endl;return -1;}sprintf(result+strlen(result),"Transport: RTP/AVP;unicast;client_port=%d-%d\r\n",rtp_port_video_, rtcp_port_video_);}else if(std::string(url) == audio_url_){if(CreateRtpSockets(&rtp_sd_audio_, &rtcp_sd_audio_, &rtp_port_audio_, &rtcp_port_audio_) < 0){std::cout << "audio CreateRtpSockets error" << std::endl;return -1;}sprintf(result+strlen(result),"Transport: RTP/AVP;unicast;client_port=%d-%d\r\n",rtp_port_audio_, rtcp_port_audio_);}else{return -1;}}else{if(std::string(url) == video_url_)sprintf(result+strlen(result),"Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", sig0_video_, sig0_video_ + 1);else if(std::string(url) == audio_url_)sprintf(result+strlen(result),"Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", sig0_audio_, sig0_audio_ + 1);elsereturn -1;}// 5. 结束请求sprintf(result+strlen(result),"\r\n");cseq++;// 6. 发送请求int ret = send(rtsp_sd_, result, strlen(result), 0);
#ifdef RTSP_DEBUGstd::cout <<  __FILE__ << __LINE__ << std::endl;std::cout <<  result << std::endl;
#endifreturn ret;
}

PLAY 请求和响应

请求格式

PLAY rtsp://example.com/media RTSP/1.0
CSeq: 4
Range: npt=0.000-
  • Method: PLAY
  • Request-URI: rtsp://example.com/media
  • RTSP-Version: RTSP/1.0
  • Headers:
    • CSeq: 4
    • Range: npt=0.000-
// (构建请求)
// PLAY rtsp://example.com/media RTSP/1.0
// CSeq: 4
// Range: npt=0.000-
int RtspClient::SendPLAY(const char *url){char result[512] = {0};sprintf(result, "PLAY %s RTSP/1.0\r\n""CSeq: %d\r\n""User-Agent: %s\r\n",url,cseq,USER_AGENT);std::string authenticate;if(!realm_.empty() && !nonce_.empty()){std::string response = GenerateAuthResponse(url_info_.username.c_str(), url_info_.password.c_str(), realm_.c_str(), nonce_.c_str(), url, "PLAY");authenticate = GenerateAuthHeader(url, response);}if(!authenticate.empty()){sprintf(result+strlen(result),"Authorization: %s\r\n",authenticate.c_str());}if(!session_.empty()){sprintf(result+strlen(result),"Session: %s\r\n",session_.c_str());}sprintf(result+strlen(result),"Range: npt=0.000-\r\n");sprintf(result+strlen(result),"\r\n");cseq++;int ret = send(rtsp_sd_, result, strlen(result), 0);return ret;
}

解析RTSP响应消息

RTSP/1.0 200 OK
CSeq: 4
Range: npt=0.000-
Session: 12345678
  • RTSP-Version: RTSP/1.0
  • Status-Code: 200
  • Reason-Phrase: OK
  • Headers:
    • CSeq: 4
    • Range: npt=0.000-
    • Session: 12345678
int RtspClient::DecodePLAY(const char *url, const char *buffer, int len){std::string str = buffer;struct ResponseMessage parsed_message;int used_bytes = ParseRTSPMessage(str, parsed_message);buffer_cmd_used_ += used_bytes;if(parsed_message.code < 0){ // internal errorreturn -1;}std::string session = GetValueByKey(parsed_message.result, "Session");if(session.empty()){buffer_cmd_used_ -= used_bytes;return 0;}int pos = session.find("timeout=");if(pos != std::string::npos){int pos1 = session.find(';', pos);std::string timeout_str;if(pos1 == std::string::npos){timeout_str = session.substr(pos + strlen("timeout="));}else{timeout_str = session.substr(pos + strlen("timeout="), pos1 - pos - strlen("timeout="));}timeout_ = atoi(timeout_str.c_str());}rtsp_cmd_stat_ = RTSPCMDSTAT::RTSP_PLAYING;return 0;
}

鉴权

主要流程

基本认证流程

  • 客户端首先发送没有认证的请求:也就是发送一个RTSP请求,该请求中不包含认证信息
  • 服务端返回401响应:此时如果服务器需要认证,同时客户端没有提供有效的认证信息,那么服务器就会返回401的鉴权状态码
  • 客户端解析401响应和WWW-Authenticate头字段
    • 首先是检查状态码是否为401
    • 然后解析头字段,确定服务器要求的认证方案和realm
    • 最后获取用户名和密码,此处的密码信息一般是MD5的方法
  • 服务器开始验证鉴权信息,如果是正确的则返回200Ok响应

整体事例代码 

---------------C->S--------------
OPTIONS rtsp://192.168.10.20:8554/stream1 RTSP/1.0
CSeq: 1
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)---------------S->C--------------
RTSP/1.0 200 OK
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, PLAY---------------C->S--------------
DESCRIBE rtsp://192.168.10.20:8554/stream1 RTSP/1.0
CSeq: 2
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)
Accept: application/sdp---------------S->C--------------
RTSP/1.0 401 Unauthorized
CSeq: 2
WWW-Authenticate: Digest realm="rtsp-server", nonce="abc123def456gh7890ijklmn"---------------C->S--------------
DESCRIBE rtsp://192.168.10.20:8554/stream1 RTSP/1.0
CSeq: 3
Authorization: Digest username="user123", realm="rtsp-server", nonce="abc123def456gh7890ijklmn", uri="rtsp://192.168.10.20:8554/stream1", response="c8ab55e7b7dfab0f5892f7feec15a0c4"
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)
Accept: application/sdp---------------S->C--------------
RTSP/1.0 200 OK
CSeq: 3
Content-Base: rtsp://192.168.10.20:8554/stream1
Content-type: application/sdp
Content-length: 450v=0
o=- 1234567890 1 IN IP4 192.168.10.20
c=IN IP4 192.168.10.20
t=0 0
a=control:*
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1
a=control:track0
m=audio 0 RTP/AVP 97
a=rtpmap:97 MPEG4-GENERIC/44100/2
a=fmtp:97 streamtype=5;profile-level-id=1;mode=AAC-hbr;config=1390;sizelength=13;indexlength=3;indexdeltalength=3
a=control:track1---------------C->S--------------
SETUP rtsp://192.168.10.20:8554/stream1/track0 RTSP/1.0
CSeq: 4
Authorization: Digest username="user123", realm="rtsp-server", nonce="abc123def456gh7890ijklmn", uri="rtsp://192.168.10.20:8554/stream1", response="e49b4c00b4b4df54c57499071a9f5b2d"
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)
Transport: RTP/AVP;unicast;client_port=7000-7001---------------S->C--------------
RTSP/1.0 200 OK
CSeq: 4
Transport: RTP/AVP;unicast;client_port=7000-7001;server_port=1500-1501
Session: 98765432---------------C->S--------------
SETUP rtsp://192.168.10.20:8554/stream1/track1 RTSP/1.0
CSeq: 5
Authorization: Digest username="user123", realm="rtsp-server", nonce="abc123def456gh7890ijklmn", uri="rtsp://192.168.10.20:8554/stream1", response="e49b4c00b4b4df54c57499071a9f5b2d"
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)
Transport: RTP/AVP;unicast;client_port=7002-7003
Session: 98765432---------------S->C--------------
RTSP/1.0 200 OK
CSeq: 5
Transport: RTP/AVP;unicast;client_port=7002-7003;server_port=1502-1503
Session: 98765432---------------C->S--------------
PLAY rtsp://192.168.10.20:8554/stream1 RTSP/1.0
CSeq: 6
Authorization: Digest username="user123", realm="rtsp-server", nonce="abc123def456gh7890ijklmn", uri="rtsp://192.168.10.20:8554/stream1", response="9bc56a3b94edc79f54371f0ea1a56fc7"
User-Agent: VLC/3.0.11.1 (LIVE555 Streaming Media v2020.01.01)
Session: 98765432
Range: npt=0.000----------------S->C--------------
RTSP/1.0 200 OK
CSeq: 6
Range: npt=0.000-
Session: 98765432; timeout=60

代码实现

生成认证头

std::string RtspClient::GenerateAuthHeader(std::string url, std::string response){// Authorization: Digest username="admin", realm="_", nonce="10839044", uri="rtsp://192.168.0.49:554/11", response="f1bf854a901dc8a7379ff277ce1be0e3"std::string str = std::string("Digest username=\"") + url_info_.username + std::string("\", realm=\"") + realm_ + std::string("\", nonce=\"") + nonce_ + std::string("\", uri=\"") + url + std::string("\", response=\"") + response + std::string("\"");return str;
}

测试

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <sstream>   // 用于 std::istringstream
#include <cstring>   // 用于 strlen
#include <cctype>    // 用于 std::isspace// 模拟 GetLineFromBuf 和 ParseRTSPLine 函数
char *GetLineFromBuf(char *buf, char *line, int buf_len) {std::cout << "[GetLineFromBuf] 开始读取新的一行,剩余长度: " << buf_len << "\n";while ((buf_len > 0) && (*buf != '\n')) {*line = *buf;line++;buf++;buf_len--;}*line = '\n';++line;*line = '\0';if (buf_len > 0) {++buf;}std::cout << "[GetLineFromBuf] 读取的行内容: " << line - strlen(line) << "\n";return buf;
}bool ParseRTSPLine(const std::string& line, std::string& key, std::string& value) {std::cout << "[ParseRTSPLine] 正在处理行内容: " << line << "\n";size_t pos = line.find(':');if (pos == std::string::npos) {std::cout << "[ParseRTSPLine] 行中未找到 ':' 字符\n";return false;}key = line.substr(0, pos);// 跳过 ':' 后的空格size_t start = pos + 1;while (start < line.size() && std::isspace(line[start])) {++start;}value = line.substr(start);std::cout << "[ParseRTSPLine] 解析出的键: [" << key << "], 值: [" << value << "]\n";return true;
}struct ResponseMessage {int code;bool find_payload;std::vector<std::pair<std::string, std::string>> result;std::string message;
};struct RTSPUrlInfo {std::string url;std::string username;std::string password;std::string host;int port;
};bool ParseRTSPUrl(const std::string& rtsp_url, RTSPUrlInfo& url_info) {std::cout << "[ParseRTSPUrl] 开始解析 URL: " << rtsp_url << "\n";std::istringstream iss(rtsp_url);char delimiter;// 检查是否以 "rtsp://" 开头if (!(iss >> delimiter) || delimiter != 'r' ||!(iss >> delimiter) || delimiter != 't' ||!(iss >> delimiter) || delimiter != 's' ||!(iss >> delimiter) || delimiter != 'p' ||!(iss >> delimiter) || delimiter != ':' ||!(iss >> delimiter) || delimiter != '/' ||!(iss >> delimiter) || delimiter != '/') {std::cout << "[ParseRTSPUrl] 无效的 RTSP URL 格式,缺少 rtsp://\n";return false;}url_info.url = "rtsp://";std::streampos pos = iss.tellg();std::string str = rtsp_url.substr(static_cast<int>(pos));// 处理用户名和密码(如果存在)if (str.find('@') != std::string::npos) {std::getline(iss, url_info.username, ':');  // 获取用户名std::getline(iss, url_info.password, '@');    // 获取密码std::cout << "[ParseRTSPUrl] 找到凭证 - 用户名: " << url_info.username << ", 密码: " << url_info.password << "\n";}pos = iss.tellg();str = rtsp_url.substr(static_cast<int>(pos));url_info.url += str;if (str.find(':') != std::string::npos) {std::getline(iss, url_info.host, ':');  // 获取主机地址if (url_info.host.empty()) {std::cout << "[ParseRTSPUrl] 主机地址为空\n";return false;}std::cout << "[ParseRTSPUrl] 解析出的主机地址: " << url_info.host << "\n";std::string port;std::getline(iss, port, ':');  // 获取端口号url_info.port = atoi(port.c_str());std::cout << "[ParseRTSPUrl] 解析出的端口: " << url_info.port << "\n";} else if (str.find('/') != std::string::npos) {std::getline(iss, url_info.host, '/');  // 获取主机地址url_info.port = 554; // 默认端口号 554std::cout << "[ParseRTSPUrl] 解析出的主机地址: " << url_info.host << ", 使用默认端口 " << url_info.port << "\n";} else {url_info.host = str;url_info.port = 554; // 默认端口号 554std::cout << "[ParseRTSPUrl] 解析出的主机地址: " << url_info.host << ", 使用默认端口 " << url_info.port << "\n";}return true;
}int ParseRTSPMessage(const std::string& rtsp_message, struct ResponseMessage &response) {std::cout << "[ParseRTSPMessage] 开始解析 RTSP 消息:\n" << rtsp_message << "\n";int used = 0; std::vector<std::pair<std::string, std::string>> result;char line[1024];char *buffer_end = const_cast<char*>(rtsp_message.c_str()) + rtsp_message.size() - 1;char *buffer_ptr = const_cast<char*>(rtsp_message.c_str());char state_buffer[512] = {0};response.code = -1;response.find_payload = false;while (buffer_ptr < buffer_end) { // 跳过状态行buffer_ptr = GetLineFromBuf(buffer_ptr, line, buffer_end - buffer_ptr);}while (buffer_ptr < buffer_end) {buffer_ptr = GetLineFromBuf(buffer_ptr, line, buffer_end - buffer_ptr);used += strlen(line);std::cout << "[ParseRTSPMessage] 读取的行内容: " << line << "\n";std::string key;std::string value;int line_len = strlen(line);line[line_len-2] = '\0';  // 移除 \r\nParseRTSPLine(line, key, value);result.emplace_back(key, value);}response.result = result;response.message = state_buffer;std::cout << "[ParseRTSPMessage] 解析出的响应码: " << response.code << ", 消息: " << response.message << "\n";return used;
}// 测试函数
void TestParseRTSP() {// 测试 ParseRTSPUrlRTSPUrlInfo url_info;std::string rtsp_url = "rtsp://username:password@192.168.1.1:554/stream";assert(ParseRTSPUrl(rtsp_url, url_info));assert(url_info.username == "username");assert(url_info.password == "password");assert(url_info.host == "192.168.1.1");assert(url_info.port == 554);// 测试 ParseRTSPMessageResponseMessage response;std::string rtsp_message = "RTSP/1.0 200 OK\r\nCSeq: 1\r\nContent-Length: 0\r\n\r\n";int bytes_used = ParseRTSPMessage(rtsp_message, response);assert(response.code == 200); // 检查响应码assert(response.message == "OK"); // 检查响应消息assert(response.result.size() == 2); // 应该有两个 header 字段assert(bytes_used == rtsp_message.size()); // 检查已读取字节数
}int main() {TestParseRTSP();std::cout << "Test passed!" << std::endl;return 0;
}

相关文章:

【RTSP】客户端(一):RTSP协议实现

概述 RTSP主要功能总结 RTSP本质是一个应用层协议&#xff0c;主要用于控制实时数据的传递&#xff0c;例如音视频流。RTSP的传输方式与HTTP类似&#xff0c;与HTTP不同在于RTSP主要用于控制传输媒体服务器上的流媒体会话。所以其是一个 客户端-服务器模型&#xff0c;客户端需…...

SpringBoot(一)--搭建架构5种方法

目录 一、⭐Idea从spring官网下载打开 2021版本idea 1.打开创建项目 2.修改pom.xml文件里的版本号 2017版本idea 二、从spring官网下载再用idea打开 三、Idea从阿里云的官网下载打开 ​编辑 四、Maven项目改造成springboot项目 五、从阿里云官网下载再用idea打开 Spri…...

面试之《commonjs,requirejs和es6 Module的区别》

设计理念 CommonJS&#xff1a;是为服务器端环境设计的模块化规范&#xff0c;以同步加载模块为核心思想。服务器端读取文件速度快&#xff0c;同步加载不会造成明显性能问题&#xff0c;方便开发者在代码执行前就确定模块间的依赖关系&#xff0c;便于管理和维护。RequireJS&…...

【工控】线扫相机小结 第五篇

背景介绍 线扫相机通过光栅尺的脉冲触发&#xff0c; 我在调试线扫过程中&#xff0c;发现图像被拉伸&#xff0c;预设调节分配器。图像正常后&#xff0c;我提高的相机的扫描速度&#xff08;Y轴动的更快了&#xff09;。 动的更快的发现&#xff0c;图像变短了&#xff08;以…...

【STM32F103C8T6】DMA数据转运ADC多通道

前言 本节为代码部分&#xff0c;知识点在这【江协科技STM32】DMA直接存储器存储-学习笔记-CSDN博客 查看数据地址&#xff1a; uint8_t aa 0x88;int main(void) {OLED_Init();OLED_ShowHexNum(1,1,aa,4); //显示十六进制数 OLED_ShowHexNum(2,1,(uint32_t)&aa,8);wh…...

[Web]ServletContext域(Application)

简介 Web应用的Application域的实现是通过ServletContext对象实现的。整个Web应用程序的所有资源共享这个域。生命周期与Web应用程序相同&#xff0c;即当前Web应用程序启动时&#xff08;以服务器视角而非访客视角&#xff09;出生&#xff0c;Web应用服务程序关闭时停止。 通…...

计算机网络--访问一个网页的全过程

文章目录 访问一个网页的全过程应用层在浏览器输入URL网址http://www.aspxfans.com:8080/news/index.aspboardID5&ID24618&page1#r_70732423通过DNS获取IP地址生成HTTP请求报文应用层最后 传输层传输层处理应用层报文建立TCP连接传输层最后 网络层网络层对TCP报文进行处…...

JVM G1垃圾回收器详细解析

G1内存布局 Garbage First(简称G1)收集器摒弃了传统垃圾收集器的严格的内存划分&#xff0c;而是采用了基于Region的内存布局形式和局部回收的设计思路。 G1垃圾收集器把Java堆划分为2048个大小相等的独立的Region&#xff0c;每个Region大小取值范围为1-32MB&#xff0c;且必…...

OpenGL中绘制图形元素的实现(使用visual studio(C++)绘制一个矩形)

目标&#xff1a;使用OpenGL提供的函数绘制矩形、线段、三角形等基本图形元素 所需效果 实验步骤 1、配置OpenGL&#xff08;详情参见OpenGL的配置&#xff09; 2、头文件引入 #include <gl/glut.h> 3、编写方法体 1>矩形实现 //绘制矩形 void DisplayRectangl…...

datax-coud部署

centos7系统环境安装 jdk1.8安装 cd /usr/local 上传jdk文件到/usr/local目录下解压缩 tar -zxvf jdk-8u261-linux-x64.tar.gz# 配置环境变量 vim /etc/profileexport JAVA_HOME=/usr/local/jdk1.8.0_261 export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/lib export PATH=$JAVA_…...

数据库---sqlite3

数据库&#xff1a; 数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查)效率低 2.数据库对数据管理效率高,使用方便 常用数据库: 1.关系型数据库: 将复杂的数据结构简化为二维表格形式 大型:Oracle、DB2 中型:MySql、SQLServer …...

Android StrictMode 使用与原理深度解析

Android StrictMode 是 Android 系统提供的一种开发者工具&#xff0c;用于检测应用主线程中不合理的耗时操作&#xff08;如磁盘 I/O、网络请求等&#xff09;和内存泄漏问题。通过配置策略和惩罚机制&#xff0c;它帮助开发者在早期发现潜在性能问题&#xff0c;提升应用流畅…...

js和java中方法重载(js本身是不支持方法重载,方便对比学习)

js如果需要实现方法重载 示例 1&#xff1a;根据参数数量实现重载 function overloadExample() {if (arguments.length 1) {console.log(一个参数:, arguments[0]);} else if (arguments.length 2) {console.log(两个参数:, arguments[0], arguments[1]);} else {console.l…...

代理模式的C++实现示例

核心思想 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;其核心思想是为其他对象提供一个代理或占位符&#xff0c;以控制对这个对象的访问。代理对象通常会在客户端和目标对象之间起到中介作用&#xff0c;可以在不改变目标对象的情况下&am…...

【阿里云】控制台使用指南:从创建ECS到系统诊断测评

前言 随着云计算技术的快速发展&#xff0c;越来越多的企业和开发者开始使用云服务来部署和管理应用程序。在众多云服务提供商中&#xff0c;阿里云&#xff08;Alibaba Cloud&#xff09;凭借其强大的基础设施和丰富的服务&#xff0c;成为了众多用户的首选。本文旨在介绍如何…...

简易的微信聊天网页版【项目测试报告】

文章目录 一、项目背景二、项目简介登录功能好友列表页面好友会话页面 三、测试工具和环境四、测试计划测试用例部分人工手动测试截图web自动化测试测试用例代码框架配置内容代码文件&#xff08;Utils.py&#xff09;登录页面代码文件&#xff08;WeChatLogin.py&#xff09;好…...

显示篇(2)- DRM A733 多显主副显绑定

通过hal层根据优先级绑定&#xff0c;优先级越高送显越靠前。&#xff08;sdk默认mipi优先级最高为主显&#xff09; 1.双显 如edp主mipi副&#xff0c;edp优先级搞。 更改如下 diff --git a/hwc-hal/drm/drmConnector.cpp b/hwc-hal/drm/drmConnector.cpp --- a/hwc-hal/d…...

基于腾讯云高性能HAI-CPU的跨境电商客服助手全链路解析

跨境电商的背景以及痛点 根据Statista数据&#xff0c;2025年全球跨境电商市场规模预计达6.57万亿美元&#xff0c;年增长率保持在12.5% 。随着平台规则趋严&#xff08;如亚马逊封店潮&#xff09;&#xff0c;更多卖家选择自建独立站&#xff0c;2024年独立站占比已达35%。A…...

北京迅为RK3568开发板OpenHarmony系统南向驱动开发内核HDF驱动框架架构

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…...

从0到1入门Docker

一、快速入门 Docker run命令中的常见参数 -d&#xff1a;让容器后台运行--name&#xff1a;给容器命名&#xff08;唯一&#xff09;-e&#xff1a;环境变量-p&#xff1a;宿主机端口映射到容器内端口镜像名称结构&#xff1a;Repository &#xff1a;TAG&#xff08;镜像名&…...

应用篇| 抓包工具-charles的使用

上文说到,我们app爬虫要借助一些抓包工具,本节课就教大家如何使用抓包工具分析app的流量。抓包工具的使用是app爬虫的必修课。相比 Fiddler 来说,Charles 的功能更强大,而且跨平台支持更好。 charles安装 官方网站:https://www.charlesproxy.com 下载链接:Download a F…...

Docker搭建Redis哨兵模式【一主两从三哨兵】

Docker搭建Redis哨兵模式 系统: CentOS 7 Dockder 版本: VMware虚拟机 网络适配器 网络连接 桥接模式:直接连接物理网络查看IP命令 ip addr一、哨兵模式概述 1. 官方文档与关联博客 官方文档:https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel关联博…...

labview实现大小端交换移位

在解码时遇到了大小端交换的问题&#xff0c;需要把高低字节的16进制值进行互换&#xff0c;这里一时间不知道怎么操作&#xff0c;本来打算先把16进制转字节数组&#xff0c;算出字节数组的大小&#xff0c;然后通过模2得到0&#xff0c;1&#xff0c;来判断是否为奇数位和偶数…...

Three.js 进阶(灯光阴影关系和设置、平行光、阴影相机)

本篇主要学习内容 : 灯光与阴影聚光灯点光源平行光阴影相机和阴影计算投射阴影接受阴影 点赞 关注 收藏 学会了 1.灯光与阴影 1、材质要满足能够对光有反应 2、设置渲染器开启阴影计算 renderer.shadowMap.enabledtrue 3、设置光照投射阴影 directionalLight.castShadow …...

FastAPI 分页模块实现详解

1. 简介 本文详细介绍了一个基于 FastAPI 框架的通用分页处理模块的实现。该模块提供了标准的分页参数处理、数据切片和响应格式化功能&#xff0c;可以轻松地集成到任何 FastAPI 项目中。 2. 代码实现 2.1 导入必要的模块 首先&#xff0c;我们需要导入所需的模块&#xf…...

RK3588部署YOLOv8(2):OpenCV和RGA实现模型前处理对比

目录 前言 1. 结果对比 1.1 时间对比 1.2 CPU和NPU占用对比 2. RGA实现YOLO前处理 2.1 实现思路 2.2 处理类的声明 2.3 处理类的实现 总结 前言 RK平台上有RGA (Raster Graphic Acceleration Unit) 加速&#xff0c;使用RGA可以减少资源占用、加速图片处理速度。因此…...

打造智能钉钉机器人:借助智谱GLM-4-Flash实现高效智能回复(文末附源码)

文章目录 前言一、准备工作&#xff08;一&#xff09;钉钉机器人&#xff08;二&#xff09;智谱 GLM-4-Flash&#xff08;三&#xff09;内网穿透工具 cpolar&#xff08;四&#xff09;需要准备的工具和环境 二、钉钉机器人的创建与配置步骤1&#xff1a;创建钉钉机器人步骤…...

使用Mermaid语法绘制的C语言程序从Linux移植到Windows的流程图

以下是使用Mermaid语法绘制的C语言程序从Linux移植到Windows的流程图&#xff1a; graph TDA[开始移植] --> B[代码兼容性检查]B --> C[检查系统调用差异\nfork/exec -> CreateProcess]B --> D[检查文件路径格式\n/ vs \\]B --> E[检查依赖库兼容性\nPOSIX vs …...

入门到入土,Java学习 day16(算法1)

利用循环遍历来判断是否相等 二分查找/折半查找 前提条件&#xff1a;数组中的数据有序 每次排除一般的查找范围 用min,max,mid来处理&#xff0c;最大加最小除2&#xff0c;比较&#xff0c;然后得到在中间左边还是右边然后更新最大最小 public class Two {// 二分查找方法…...

Vulnhub 靶机 VulnOSv2 write up opendocman cms 32075 sql注入 账号密码 ssh连接 37292.c 脏牛提权

Vulnhub 靶机 VulnOSv2 write up opendocman cms 32075 sql注入 账号密码 ssh连接 37292.c 脏牛提权 一、信息收集 1、首先拿到靶场先扫一下ip arp-scan -l 3、 2、指纹扫描 nmap -sS -sV 192.168.66.178nmap -p- -sV -A 192.168.66.253 PORT STATE SERVICE VERSION 22…...