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

概述

RTSP主要功能总结

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

主要功能

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

请求格式

bash 复制代码
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 头字段

响应格式

bash 复制代码
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 请求和响应

请求格式

bash 复制代码
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

响应格式

bash 复制代码
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

代码实现

cpp 复制代码
// (请求格式)
// 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_DEBUG
    std::cout <<  __FILE__ << __LINE__ << std::endl;
    std::cout <<  result << std::endl;
#endif
    cseq++;
    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 error
        return -1;
    }
    rtsp_cmd_stat_ = RTSPCMDSTAT::RTSP_DESCRIBE;
    return 0;
}

DESCRIBE 请求和响应

请求格式

bash 复制代码
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

响应格式

python 复制代码
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
  • RTSP-Version: RTSP/1.0
  • Status-Code: 200
  • Reason-Phrase: OK
  • Headers :
    • CSeq: 2
    • Content-Type: application/sdp
    • Content-Length: 158
  • Message-Body: SDP 信息,描述媒体会话

解析响应

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

cpp 复制代码
// (请求格式)
// 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_DEBUG
    std::cout <<  __FILE__ << __LINE__ << std::endl;
    std::cout <<  result << std::endl;
#endif
    return 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 error
        return -1;
    }
    if(parsed_message.code == 401){ // Unauthorized
        std::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 请求和响应

请求格式

python 复制代码
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

解析响应

python 复制代码
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

代码实现

cpp 复制代码
// (构建请求)
// 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);
        else
            return -1;
    }

    // 5. 结束请求
    sprintf(result+strlen(result),"\r\n");
    cseq++;

    // 6. 发送请求
    int ret = send(rtsp_sd_, result, strlen(result), 0);
#ifdef RTSP_DEBUG
    std::cout <<  __FILE__ << __LINE__ << std::endl;
    std::cout <<  result << std::endl;
#endif
    return ret;
}

PLAY 请求和响应

请求格式

python 复制代码
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-
cpp 复制代码
// (构建请求)
// 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响应消息

python 复制代码
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
cpp 复制代码
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 error
        return -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响应

整体事例代码

cpp 复制代码
---------------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: 450

v=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

代码实现

生成认证头

cpp 复制代码
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;
}

测试

cpp 复制代码
#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; // 默认端口号 554
        std::cout << "[ParseRTSPUrl] 解析出的主机地址: " << url_info.host << ", 使用默认端口 " << url_info.port << "\n";
    } else {
        url_info.host = str;
        url_info.port = 554; // 默认端口号 554
        std::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\n
        ParseRTSPLine(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() {
    // 测试 ParseRTSPUrl
    RTSPUrlInfo url_info;
    std::string rtsp_url = "rtsp://username:[email protected]: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);

    // 测试 ParseRTSPMessage
    ResponseMessage 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;
}
相关推荐
小刘同学++3 分钟前
ECB(电子密码本,Electronic Codebook) 和 CBC(密码分组链接,Cipher Block Chaining)区分于用途
网络·ssl
gadiaola37 分钟前
【计算机网络】第3章:传输层—TCP 拥塞控制
网络·网络协议·tcp/ip·计算机网络
猎板PCB厚铜专家大族43 分钟前
多层PCB技术解析:从材料选型到制造工艺的深度实践
网络·制造
FakeOccupational2 小时前
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
网络·游戏
fei_sun4 小时前
【计算机网络】三报文握手建立TCP连接
网络·tcp/ip·计算机网络
Johny_Zhao5 小时前
2025年6月Docker镜像加速失效终极解决方案
linux·网络·网络安全·docker·信息安全·kubernetes·云计算·containerd·yum源·系统运维
PypYCCcccCc5 小时前
支付系统架构图
java·网络·金融·系统架构
搬码临时工7 小时前
如何把本地服务器变成公网服务器?内网ip网址转换到外网连接访问
运维·服务器·网络·tcp/ip·智能路由器·远程工作·访问公司内网
zzc9217 小时前
MATLAB仿真生成无线通信网络拓扑推理数据集
开发语言·网络·数据库·人工智能·python·深度学习·matlab
朱包林10 小时前
day27-shell编程(自动化)
linux·运维·服务器·网络·shell脚本