【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;
}
相关推荐
lisw051 小时前
网络化:DevOps 工程的必要基础(Networking: The Essential Foundation for DevOps Engineering)
网络·devops
驱动小百科3 小时前
WiFi出现感叹号上不了网怎么办 轻松恢复网络
网络·智能路由器·wifi出现感叹号怎么解决·wifi无法上网·电脑wifi
好多知识都想学3 小时前
协议路由与路由协议
网络·智能路由器
SZ1701102313 小时前
中继器的作用
服务器·网络·智能路由器
Huazzi.5 小时前
Ubuntu 22虚拟机【网络故障】快速解决指南
linux·网络·学习·ubuntu·bash·编程
熙曦Sakura5 小时前
【Linux网络】HTTP
linux·网络·http
毒果5 小时前
网络安全:账号密码与诈骗防范
网络·安全·web安全
八股文领域大手子5 小时前
SSL/TLS 证书与数字签名:构建互联网信任的详解
网络·网络协议·ssl
学渣676566 小时前
TCP/IP 模型每层的封装格式
网络