293 lines
12 KiB
C
293 lines
12 KiB
C
|
|
#ifndef SRCCTL_RTSP_DATA_CHANNEL_H
|
|||
|
|
#define SRCCTL_RTSP_DATA_CHANNEL_H
|
|||
|
|
|
|||
|
|
#include "Socket/CCServerSocket.h"
|
|||
|
|
#include "Socket/CCDatagramSocket.h"
|
|||
|
|
#include "CCSDP.h"
|
|||
|
|
|
|||
|
|
#define CC_R_RTSP_BUFFER_SIZE 1
|
|||
|
|
|
|||
|
|
namespace CTL{
|
|||
|
|
typedef struct RTSP_Message{
|
|||
|
|
int statusCode = 0; // 状态码 (响应时使用)
|
|||
|
|
int port = 0; // 客户端端口号
|
|||
|
|
bool isRequest = true; // 是否为请求消息(true)还是响应消息(false)
|
|||
|
|
String client_ip; // 客户端IP
|
|||
|
|
String url_data; // 请求URL
|
|||
|
|
String method; // RTSP方法 (OPTIONS, DESCRIBE, SETUP, PLAY等)
|
|||
|
|
String uri; // 请求URI
|
|||
|
|
String uri_path; // 请求URI路径
|
|||
|
|
String version; // RTSP版本 (如"RTSP/1.0")
|
|||
|
|
String reasonPhrase; // 原因短语 (响应时使用)
|
|||
|
|
std::map<String, String> headers; // 头部字段
|
|||
|
|
String body; // 消息体
|
|||
|
|
String sdp_content; // SDP内容
|
|||
|
|
} RTSP_Request;
|
|||
|
|
class RTSPDataChannel{
|
|||
|
|
ClientSocket* socket_t = nullptr;
|
|||
|
|
public:
|
|||
|
|
explicit RTSPDataChannel(ClientSocket* socket){
|
|||
|
|
socket_t = socket;
|
|||
|
|
}
|
|||
|
|
~RTSPDataChannel(){
|
|||
|
|
if (socket_t) {
|
|||
|
|
socket_t->close();
|
|||
|
|
socket_t = nullptr;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
static void MessagePrint(const String& message,...){
|
|||
|
|
System::Println(message);
|
|||
|
|
}
|
|||
|
|
int receive(char *buffer, const size_t size) const {
|
|||
|
|
if (socket_t && socket_t->isConnect()) {
|
|||
|
|
const auto input = socket_t->getInputStream();
|
|||
|
|
if (input->available()) {
|
|||
|
|
return input->read(buffer, size);
|
|||
|
|
}
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
int write(const char *buffer, const size_t size) const {
|
|||
|
|
if (socket_t && socket_t->isConnect()) {
|
|||
|
|
const auto output = socket_t->getOutputStream();
|
|||
|
|
return output->write(buffer,size);
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
String readHeader() const{
|
|||
|
|
if (this->socket_t) {
|
|||
|
|
String data;
|
|||
|
|
data.reserve(4096); // 预分配内存
|
|||
|
|
int ET = 1;
|
|||
|
|
while (true){
|
|||
|
|
char buffer[CC_R_RTSP_BUFFER_SIZE] = {0};
|
|||
|
|
if(const auto length = receive(buffer,CC_R_RTSP_BUFFER_SIZE); length > 0){
|
|||
|
|
data.append(buffer, length); // 块追加代替逐个字符处理
|
|||
|
|
if (const auto pos = data.find("\r\n\r\n"); pos != CTL::String::npos) {
|
|||
|
|
data.resize(pos + 4); // 截断到header结束位置
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (length == -2) {
|
|||
|
|
if (ET >= 1000 * 1000) {
|
|||
|
|
this->MessagePrint("RTSPDataChannel readHeader -> Timeout");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
ET++;
|
|||
|
|
Thread::Sleep(1);
|
|||
|
|
}
|
|||
|
|
else{
|
|||
|
|
return "close";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
return "Error_10030";
|
|||
|
|
}
|
|||
|
|
String readData(const uint64_t length) const{
|
|||
|
|
if (this->socket_t) {
|
|||
|
|
String data;
|
|||
|
|
data.reserve(length); // 预分配内存
|
|||
|
|
int ET = 1;
|
|||
|
|
while (true){
|
|||
|
|
char buffer[CC_R_RTSP_BUFFER_SIZE] = {0};
|
|||
|
|
if(const auto len = receive(buffer,CC_R_RTSP_BUFFER_SIZE); len > 0){
|
|||
|
|
data.append(buffer, len);
|
|||
|
|
if (data.length() >= length) {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (len == -2) {
|
|||
|
|
if (ET >= 1000 * 300) {
|
|||
|
|
this->MessagePrint("RTSPDataChannel readData -> Timeout");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
ET++;
|
|||
|
|
Thread::Sleep(1);
|
|||
|
|
}
|
|||
|
|
else{
|
|||
|
|
return "close";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
return "Error_10030";
|
|||
|
|
}
|
|||
|
|
static RTSP_Message ParseRTSP(const String& data){
|
|||
|
|
RTSP_Message msg;
|
|||
|
|
if (data.empty()) {
|
|||
|
|
return msg;
|
|||
|
|
}
|
|||
|
|
msg.url_data = data;
|
|||
|
|
// 查找空行(\r\n\r\n)来分离头部和消息体
|
|||
|
|
size_t headerEndPos = data.find("\r\n\r\n");
|
|||
|
|
String headersPart;
|
|||
|
|
String bodyPart;
|
|||
|
|
if (headerEndPos != CTL::String::npos) {
|
|||
|
|
headersPart = data.substr(0, headerEndPos);
|
|||
|
|
bodyPart = data.substr(headerEndPos + 4); // 跳过"\r\n\r\n"
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
headersPart = data;
|
|||
|
|
}
|
|||
|
|
// 分割头部行
|
|||
|
|
std::vector<String> lines;
|
|||
|
|
String currentLine;
|
|||
|
|
for (size_t i = 0; i < headersPart.length(); ++i) {
|
|||
|
|
if (headersPart[i] == '\r' && i + 1 < headersPart.length() && headersPart[i + 1] == '\n') {
|
|||
|
|
lines.push_back(currentLine);
|
|||
|
|
currentLine.clear();
|
|||
|
|
i++; // 跳过'\n'
|
|||
|
|
} else {
|
|||
|
|
currentLine += headersPart[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!currentLine.empty()) {
|
|||
|
|
lines.push_back(currentLine);
|
|||
|
|
}
|
|||
|
|
if (!lines.empty()) {
|
|||
|
|
// 解析第一行 (请求行或状态行)
|
|||
|
|
const String& firstLine = lines[0];
|
|||
|
|
|
|||
|
|
// 检查是请求还是响应
|
|||
|
|
if (firstLine.find("RTSP/") == 0) {
|
|||
|
|
// 这是响应行: "RTSP/1.0 200 OK"
|
|||
|
|
msg.isRequest = false;
|
|||
|
|
|
|||
|
|
size_t firstSpace = firstLine.find(' ');
|
|||
|
|
if (firstSpace != String::npos) {
|
|||
|
|
size_t secondSpace = firstLine.find(' ', firstSpace + 1);
|
|||
|
|
if (secondSpace != String::npos) {
|
|||
|
|
msg.version = firstLine.substr(0, firstSpace);
|
|||
|
|
try {
|
|||
|
|
msg.statusCode = std::stoi(firstLine.substr(firstSpace + 1,
|
|||
|
|
secondSpace - firstSpace - 1));
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
msg.statusCode = 500; // 默认错误状态
|
|||
|
|
}
|
|||
|
|
msg.reasonPhrase = firstLine.substr(secondSpace + 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// 这是请求行: "OPTIONS rtsp://... RTSP/1.0"
|
|||
|
|
size_t firstSpace = firstLine.find(' ');
|
|||
|
|
size_t lastSpace = firstLine.rfind(' ');
|
|||
|
|
|
|||
|
|
if (firstSpace != String::npos && lastSpace != String::npos && firstSpace < lastSpace) {
|
|||
|
|
msg.method = firstLine.substr(0, firstSpace);
|
|||
|
|
msg.uri = firstLine.substr(firstSpace + 1, lastSpace - firstSpace - 1);
|
|||
|
|
msg.version = firstLine.substr(lastSpace + 1);
|
|||
|
|
|
|||
|
|
// 从URI中解析路径部分
|
|||
|
|
size_t schemeEnd = msg.uri.find("://");
|
|||
|
|
if (schemeEnd != String::npos) {
|
|||
|
|
// 跳过协议和主机部分,找到路径
|
|||
|
|
size_t hostStart = schemeEnd + 3;
|
|||
|
|
size_t pathStart = msg.uri.find('/', hostStart);
|
|||
|
|
if (pathStart != String::npos) {
|
|||
|
|
msg.uri_path = msg.uri.substr(pathStart);
|
|||
|
|
} else {
|
|||
|
|
// 如果没有路径,则设置为根路径
|
|||
|
|
msg.uri_path = "/";
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 如果没有协议前缀,整个URI可能是路径
|
|||
|
|
if (msg.uri.empty() || msg.uri[0] != '/') {
|
|||
|
|
msg.uri_path = "/" + msg.uri;
|
|||
|
|
} else {
|
|||
|
|
msg.uri_path = msg.uri;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解析头部字段
|
|||
|
|
for (size_t i = 1; i < lines.size(); ++i) {
|
|||
|
|
const String& line = lines[i];
|
|||
|
|
size_t colonPos = line.find(':');
|
|||
|
|
if (colonPos != String::npos) {
|
|||
|
|
String key = line.substr(0, colonPos);
|
|||
|
|
String value = line.substr(colonPos + 1);
|
|||
|
|
|
|||
|
|
// 移除键和值前后的空白字符
|
|||
|
|
size_t startKey = key.find_first_not_of(" \t");
|
|||
|
|
size_t endKey = key.find_last_not_of(" \t");
|
|||
|
|
if (startKey != String::npos && endKey != String::npos) {
|
|||
|
|
key = key.substr(startKey, endKey - startKey + 1);
|
|||
|
|
} else if (startKey == String::npos) {
|
|||
|
|
continue; // 跳过无效行
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
size_t startPos = value.find_first_not_of(" \t");
|
|||
|
|
size_t endPos = value.find_last_not_of(" \t");
|
|||
|
|
if (startPos != String::npos && endPos != String::npos) {
|
|||
|
|
value = value.substr(startPos, endPos - startPos + 1);
|
|||
|
|
} else if (startPos == String::npos) {
|
|||
|
|
value.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 将头部字段转换为小写作为键
|
|||
|
|
String lowerKey = key;
|
|||
|
|
std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower);
|
|||
|
|
|
|||
|
|
msg.headers[lowerKey] = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 设置消息体
|
|||
|
|
msg.body = bodyPart;
|
|||
|
|
return msg;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
class RTSP_Response{
|
|||
|
|
RTSPDataChannel* socket_t = nullptr;
|
|||
|
|
std::map<String, String> headers;
|
|||
|
|
SDPBuilder sdp_builder;
|
|||
|
|
public:
|
|||
|
|
//-----------------------------------------------------------------------------------------
|
|||
|
|
int statusCode = 200;
|
|||
|
|
String reasonPhrase = "OK";
|
|||
|
|
bool auto_send = true;
|
|||
|
|
//-----------------------------------------------------------------------------------------
|
|||
|
|
void setSocket(RTSPDataChannel* socket){
|
|||
|
|
this->socket_t = socket;
|
|||
|
|
}
|
|||
|
|
RTSPDataChannel* getSocket() const{
|
|||
|
|
return socket_t;
|
|||
|
|
}
|
|||
|
|
String getHeader(const String& key) const{
|
|||
|
|
auto it = headers.find(key);
|
|||
|
|
if (it != headers.end()) {
|
|||
|
|
return it->second;
|
|||
|
|
}
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
void setHeader(const String& key, const String& value){
|
|||
|
|
headers[key] = value;
|
|||
|
|
}
|
|||
|
|
String getBuild() const{
|
|||
|
|
String response = String::format("RTSP/1.0 {} {}\r\n", statusCode, reasonPhrase.c_str());
|
|||
|
|
for (const auto& header : headers) {
|
|||
|
|
response.append(String::format("{}: {}\r\n",header.first.c_str(),header.second.c_str()));
|
|||
|
|
}
|
|||
|
|
response.append("\r\n");
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
bool send(){
|
|||
|
|
if(socket_t != nullptr){
|
|||
|
|
int ret = socket_t->write(getBuild().c_str(), getBuild().length());
|
|||
|
|
if (ret > 0) {
|
|||
|
|
auto_send = false;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
//-----------------------------------------------------------------------------------------
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endif
|