rtsp协议之.c++实现,rtsp,rtp协议框架,模拟多路音视频h264,265,aac,数据帧传输,接收(二)

1、RTSP 服务器核心:处理 RTSP 会话管理、请求解析和响应生成

2、媒体源抽象:定义了通用媒体源接口,支持不同类型的媒体流
具体媒体源实现:
H264MediaSource:模拟 H.264 视频流
H265MediaSource:模拟 H.265 视频流
AACMediaSource:模拟 AAC 音频流

3、RTP 包生成:根据不同媒体类型生成相应的 RTP 包
使用时,只需创建 RTSP 服务器实例,添加多个媒体源,然后启动服务器即可。服务器会模拟多个相机的码流,可以通过 RTSP 客户端访问这些模拟的媒体流。

在给出例子前,先解释一些概念。

1、会话id
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 伪代码示例(需在实际代码中实现)
std::string RTSPServer::generateSessionId() {// 生成唯一会话ID(例如随机数+时间戳)static std::atomic<uint32_t> counter(0);return std::to_string(time(nullptr)) + "_" + std::to_string(counter++);
}void RTSPServer::handleSetupRequest(const RTSPRequest& request, RTSPResponse& response) {// 为客户端分配会话IDstd::string sessionId = generateSessionId();response.setHeader("Session", sessionId);// 保存会话信息(如端口、媒体源等)SessionInfo session;session.id = sessionId;session.clientPort = parseClientPort(request.getHeader("Transport"));// ... 其他会话信息// 存储会话(通常用std::map)m_sessions[sessionId] = session;
}

在这里插入图片描述

2、
在视频流处理中,区分关键帧(I 帧)、预测帧(P 帧)和分片(Fragmentation)是保证视频正确解码和高效传输的关键。以下从原理、协议和实际应用角度详细解释:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在你的 RTSP 模拟器中,可通过以下方式模拟帧类型和分片:

class MediaSource {
public:enum FrameType {FRAME_I,FRAME_P,FRAME_B};// 生成模拟帧(需在子类实现)virtual bool getNextFrame(uint8_t*& data, size_t& size, FrameType& type) = 0;
};class H264MediaSource : public MediaSource {
public:bool getNextFrame(uint8_t*& data, size_t& size, FrameType& type) override {// 模拟:每30帧生成一个I帧,其余为P帧static int frameCounter = 0;if (frameCounter++ % 30 == 0) {type = FRAME_I;generateIFrame(data, size);} else {type = FRAME_P;generatePFrame(data, size);}return true;}
};

分片处理

class RTPGenerator {
public:void generateRtpPackets(const uint8_t* nalData, size_t nalSize, FrameType frameType, std::vector<RtpPacket>& packets) {const int MAX_RTP_PAYLOAD = 1400;if (nalSize <= MAX_RTP_PAYLOAD) {// 小NAL单元直接封装packets.emplace_back(nalData, nalSize, frameType, false);} else {// 分片封装const uint8_t* payload = nalData + 1;  // 跳过NAL头size_t payloadSize = nalSize - 1;size_t offset = 0;// 第一个分片packets.emplace_back(createFirstFragment(nalData, payload, offset, MAX_RTP_PAYLOAD));offset += MAX_RTP_PAYLOAD - 2;  // 减去FU头大小// 中间分片while (offset + MAX_RTP_PAYLOAD - 2 < payloadSize) {packets.emplace_back(createMiddleFragment(nalData, payload, offset, MAX_RTP_PAYLOAD));offset += MAX_RTP_PAYLOAD - 2;}// 最后一个分片packets.emplace_back(createLastFragment(nalData, payload, offset, payloadSize - offset));}}
};

在这里插入图片描述
在这里插入图片描述
组合后就是如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
或者

// H.264 NAL单元分片为RTP包
void fragmentNalUnit(const uint8_t* nalData, size_t nalSize, std::vector<RtpPacket>& packets) {const int MAX_PAYLOAD_SIZE = 1400;  // MTU - RTP头(12字节) - FU头(2字节)const uint8_t FU_INDICATOR = 0x7C;  // 固定为0x7C (Type=28)// 提取原始NAL类型(低5位)uint8_t originalNalType = nalData[0] & 0x1F;// 计算需要的分片数size_t numFragments = (nalSize - 1 + MAX_PAYLOAD_SIZE - 1) / MAX_PAYLOAD_SIZE;size_t offset = 1;  // 跳过原始NAL头(1字节)for (size_t i = 0; i < numFragments; i++) {size_t fragmentSize = std::min(nalSize - offset, MAX_PAYLOAD_SIZE);bool isFirst = (i == 0);bool isLast = (i == numFragments - 1);// 创建FU headeruint8_t fuHeader = originalNalType;if (isFirst) fuHeader |= 0x80;  // 设置起始位if (isLast) fuHeader |= 0x40;   // 设置结束位// 创建RTP包RtpPacket packet;packet.payloadSize = 2 + fragmentSize;  // FU指示符(1) + FU头(1) + 数据packet.payload[0] = FU_INDICATOR;packet.payload[1] = fuHeader;memcpy(packet.payload + 2, nalData + offset, fragmentSize);packets.push_back(packet);offset += fragmentSize;}
}

接收端重组逻辑
接收端需要根据 FU-A 头信息重组原始 NAL 单元
在这里插入图片描述
或者

// 重组H.264 FU-A分片
bool reassembleFuA(const uint8_t* fragment, size_t fragSize, bool isFirst, bool isLast, std::vector<uint8_t>& nalBuffer) {if (isFirst) {// 开始新的NAL单元nalBuffer.clear();// 从FU indicator中提取NRIuint8_t nri = (fragment[0] & 0x60);// 从FU header中提取原始NAL类型uint8_t originalType = fragment[1] & 0x1F;// 重建原始NAL头uint8_t originalNalHeader = nri | originalType;nalBuffer.push_back(originalNalHeader);}// 添加分片数据(跳过FU indicator和FU header)nalBuffer.insert(nalBuffer.end(), fragment + 2, fragment + fragSize);// 返回是否完成重组return isLast;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在 H.264/H.265 视频流中,判断一帧是 I 帧、P 帧还是 B 帧需要分析 NAL 单元的类型和内容。以下是具体的判断方法:

  1. 通过 NAL 单元类型判断(最直接方法)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
// 判断H.264 NAL单元是否为I帧
bool isH264IFrame(const uint8_t* nalData, size_t nalSize) {if (nalSize < 1) return false;// 提取NAL头部的Type字段(低5位)uint8_t nalType = nalData[0] & 0x1F;// 如果是IDR图像(Type=5),直接判定为I帧if (nalType == 5) return true;// 对于Type=1(非IDR图像),需要进一步分析slice_typeif (nalType == 1 && nalSize >= 5) {// 定位到slice_header中的slice_type字段// 注意:实际解析需要考虑NAL单元的RBSP解码和EBSP去封装uint8_t sliceType = parseSliceType(nalData + 1, nalSize - 1);return (sliceType == 2 || sliceType == 7);  // slice_type=2或7表示I帧}return false;
}// 判断H.264 NAL单元是否为B帧
bool isH264BFrame(const uint8_t* nalData, size_t nalSize) {if (nalSize < 1) return false;uint8_t nalType = nalData[0] & 0x1F;// 对于Type=1(非IDR图像),分析slice_typeif (nalType == 1 && nalSize >= 5) {uint8_t sliceType = parseSliceType(nalData + 1, nalSize - 1);return (sliceType == 1 || sliceType == 6);  // slice_type=1或6表示B帧}return false;
}// 解析slice_type字段(简化版)
uint8_t parseSliceType(const uint8_t* data, size_t size) {// 实际解析需要考虑:// 1. RBSP解码(移除防止竞争字节0x03)// 2. EBSP去封装// 3. 按位解析slice_header结构// 此处为简化示例,实际代码需根据H.264标准实现return data[0] & 0x07;  // 假设slice_type在第一个字节的低3位
}

在这里插入图片描述

// 处理RTP包回调
void handleRtpPacket(const uint8_t* rtpData, size_t rtpSize) {// 解析RTP头部uint16_t seqNum = (rtpData[2] << 8) | rtpData[3];uint32_t timestamp = (rtpData[4] << 24) | (rtpData[5] << 16) | (rtpData[6] << 8) | rtpData[7];// 提取RTP负载const uint8_t* payload = rtpData + 12;  // RTP头部默认12字节size_t payloadSize = rtpSize - 12;// 判断是否为FU-A分片if (payloadSize >= 2 && (payload[0] & 0x1F) == 28) {  // FU-A类型uint8_t fuHeader = payload[1];bool isStart = (fuHeader & 0x80) != 0;bool isEnd = (fuHeader & 0x40) != 0;if (isStart) {// 开始新的NAL单元重组nalBuffer.clear();// 重建原始NAL头部uint8_t originalNalHeader = (payload[0] & 0xE0) | (fuHeader & 0x1F);nalBuffer.push_back(originalNalHeader);}// 添加分片数据(跳过FU indicator和FU header)nalBuffer.insert(nalBuffer.end(), payload + 2, payload + payloadSize);if (isEnd) {// NAL单元重组完成,判断帧类型if (isH264IFrame(nalBuffer.data(), nalBuffer.size())) {printf("收到I帧,时间戳: %u\n", timestamp);} else if (isH264BFrame(nalBuffer.data(), nalBuffer.size())) {printf("收到B帧,时间戳: %u\n", timestamp);} else {printf("收到P帧,时间戳: %u\n", timestamp);}}} else {// 非分片NAL单元,直接判断if (isH264IFrame(payload, payloadSize)) {printf("收到完整I帧,时间戳: %u\n", timestamp);}}
}

在这里插入图片描述

rtsp_server.h

#ifndef RTSP_SERVER_H
#define RTSP_SERVER_H#include <string>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <memory>
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <queue>
#include <condition_variable>class RTSPSession;
class MediaSource;class RTSPServer {
public:RTSPServer(int port = 8554);~RTSPServer();bool start();void stop();void addMediaSource(const std::string& streamName, std::shared_ptr<MediaSource> source);void removeMediaSource(const std::string& streamName);private:void handleClient(int clientSocket);void handleRTSPRequest(int clientSocket, const std::string& request);std::shared_ptr<RTSPSession> createSession(const std::string& streamName);int m_serverSocket;bool m_running;std::thread m_serverThread;std::mutex m_sessionsMutex;std::map<std::string, std::shared_ptr<RTSPSession>> m_sessions;std::unordered_map<std::string, std::shared_ptr<MediaSource>> m_mediaSources;int m_nextSessionId;
};class RTSPSession {
public:RTSPSession(int sessionId, std::shared_ptr<MediaSource> mediaSource);~RTSPSession();int getSessionId() const;std::string getSessionDescription() const;void setup(int clientRtpPort, int clientRtcpPort);void play();void pause();void teardown();private:void sendRTPData();int m_sessionId;std::shared_ptr<MediaSource> m_mediaSource;int m_clientRtpPort;int m_clientRtcpPort;int m_rtpSocket;bool m_playing;std::thread m_senderThread;bool m_senderRunning;
};class MediaSource {
public:virtual ~MediaSource() = default;virtual std::string getMimeType() const = 0;virtual std::string getMediaDescription() const = 0;virtual std::string getAttribute() const = 0;virtual void getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) = 0;virtual uint32_t getClockRate() const = 0;
};#endif // RTSP_SERVER_H    

rtsp_server.cpp

#include "rtsp_server.h"
#include <iostream>
#include <sstream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <ctime>
#include <random>RTSPServer::RTSPServer(int port) : m_serverSocket(-1), m_running(false), m_nextSessionId(1000) {// 创建套接字m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);if (m_serverSocket == -1) {std::cerr << "Failed to create socket" << std::endl;return;}// 设置套接字选项int opt = 1;if (setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {std::cerr << "Failed to set socket options" << std::endl;close(m_serverSocket);m_serverSocket = -1;return;}// 绑定套接字sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(port);if (bind(m_serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {std::cerr << "Failed to bind socket" << std::endl;close(m_serverSocket);m_serverSocket = -1;return;}// 监听连接if (listen(m_serverSocket, 5) == -1) {std::cerr << "Failed to listen on socket" << std::endl;close(m_serverSocket);m_serverSocket = -1;return;}
}RTSPServer::~RTSPServer() {stop();if (m_serverSocket != -1) {close(m_serverSocket);}
}bool RTSPServer::start() {if (m_running) return true;if (m_serverSocket == -1) return false;m_running = true;m_serverThread = std::thread([this]() {while (m_running) {sockaddr_in clientAddr;socklen_t clientAddrLen = sizeof(clientAddr);int clientSocket = accept(m_serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket == -1) {if (m_running) {std::cerr << "Failed to accept client connection" << std::endl;}continue;}// 处理客户端连接std::thread([this, clientSocket]() {handleClient(clientSocket);close(clientSocket);}).detach();}});return true;
}void RTSPServer::stop() {if (!m_running) return;m_running = false;if (m_serverSocket != -1) {// 关闭服务器套接字以中断accept调用close(m_serverSocket);m_serverSocket = -1;}if (m_serverThread.joinable()) {m_serverThread.join();}// 清理所有会话std::lock_guard<std::mutex> lock(m_sessionsMutex);for (auto& session : m_sessions) {session.second->teardown();}m_sessions.clear();
}void RTSPServer::addMediaSource(const std::string& streamName, std::shared_ptr<MediaSource> source) {std::lock_guard<std::mutex> lock(m_sessionsMutex);m_mediaSources[streamName] = source;
}void RTSPServer::removeMediaSource(const std::string& streamName) {std::lock_guard<std::mutex> lock(m_sessionsMutex);m_mediaSources.erase(streamName);
}void RTSPServer::handleClient(int clientSocket) {char buffer[4096];memset(buffer, 0, sizeof(buffer));// 读取RTSP请求ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);if (bytesRead <= 0) {return;}std::string request(buffer, bytesRead);handleRTSPRequest(clientSocket, request);
}void RTSPServer::handleRTSPRequest(int clientSocket, const std::string& request) {std::istringstream iss(request);std::string method, url, version;iss >> method >> url >> version;std::stringstream response;if (method == "OPTIONS") {response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n";response << "\r\n";} else if (method == "DESCRIBE") {// 解析URL获取流名称size_t pos = url.find_last_of('/');std::string streamName = (pos != std::string::npos) ? url.substr(pos + 1) : url;std::lock_guard<std::mutex> lock(m_sessionsMutex);auto it = m_mediaSources.find(streamName);if (it == m_mediaSources.end()) {response << "RTSP/1.0 404 Not Found\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";} else {std::shared_ptr<MediaSource> mediaSource = it->second;response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Content-Type: application/sdp\r\n";response << "Content-Length: ";std::string sdp = "v=0\r\n";sdp += "o=- " + std::to_string(time(nullptr)) + " 1 IN IP4 127.0.0.1\r\n";sdp += "s=Media Session\r\n";sdp += "i=" + streamName + "\r\n";sdp += "c=IN IP4 0.0.0.0\r\n";sdp += "t=0 0\r\n";sdp += "m=video 0 RTP/AVP " + std::to_string(96) + "\r\n";sdp += "a=rtpmap:" + std::to_string(96) + " " + mediaSource->getMimeType() + "/" + std::to_string(mediaSource->getClockRate()) + "\r\n";sdp += "a=control:trackID=0\r\n";sdp += mediaSource->getAttribute() + "\r\n";response << sdp.length() << "\r\n\r\n";response << sdp;}}else if (method == "SETUP") {// 解析URL获取流名称size_t pos = url.find_last_of('/');std::string streamName = (pos != std::string::npos) ? url.substr(pos + 1) : url;// 解析客户端端口pos = request.find("client_port=");int clientRtpPort = 0, clientRtcpPort = 0;if (pos != std::string::npos) {pos += 12;size_t endPos = request.find('-', pos);if (endPos != std::string::npos) {clientRtpPort = std::stoi(request.substr(pos, endPos - pos));pos = endPos + 1;endPos = request.find('\r', pos);if (endPos != std::string::npos) {clientRtcpPort = std::stoi(request.substr(pos, endPos - pos));}}}std::lock_guard<std::mutex> lock(m_sessionsMutex);auto it = m_mediaSources.find(streamName);if (it == m_mediaSources.end()) {response << "RTSP/1.0 404 Not Found\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";} else {// 创建会话std::shared_ptr<RTSPSession> session = createSession(streamName);session->setup(clientRtpPort, clientRtcpPort);response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Session: " << session->getSessionId() << ";timeout=60\r\n";response << "Transport: RTP/AVP/UDP;unicast;client_port=" << clientRtpPort << "-" << clientRtcpPort << ";server_port=" << (clientRtpPort + 1) << "-" << (clientRtcpPort + 1) << "\r\n";response << "\r\n";}}else if (method == "PLAY") {// 解析会话IDsize_t pos = request.find("Session: ");int sessionId = 0;if (pos != std::string::npos) {pos += 9;size_t endPos = request.find('\r', pos);if (endPos != std::string::npos) {sessionId = std::stoi(request.substr(pos, endPos - pos));}}std::lock_guard<std::mutex> lock(m_sessionsMutex);auto it = m_sessions.find(std::to_string(sessionId));if (it == m_sessions.end()) {response << "RTSP/1.0 454 Session Not Found\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";} else {it->second->play();response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Session: " << sessionId << "\r\n";response << "Range: npt=0.000-\r\n";response << "\r\n";}}else if (method == "PAUSE") {// 解析会话IDsize_t pos = request.find("Session: ");int sessionId = 0;if (pos != std::string::npos) {pos += 9;size_t endPos = request.find('\r', pos);if (endPos != std::string::npos) {sessionId = std::stoi(request.substr(pos, endPos - pos));}}std::lock_guard<std::mutex> lock(m_sessionsMutex);auto it = m_sessions.find(std::to_string(sessionId));if (it == m_sessions.end()) {response << "RTSP/1.0 454 Session Not Found\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";} else {it->second->pause();response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Session: " << sessionId << "\r\n";response << "\r\n";}}else if (method == "TEARDOWN") {// 解析会话IDsize_t pos = request.find("Session: ");int sessionId = 0;if (pos != std::string::npos) {pos += 9;size_t endPos = request.find('\r', pos);if (endPos != std::string::npos) {sessionId = std::stoi(request.substr(pos, endPos - pos));}}std::lock_guard<std::mutex> lock(m_sessionsMutex);auto it = m_sessions.find(std::to_string(sessionId));if (it == m_sessions.end()) {response << "RTSP/1.0 454 Session Not Found\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";} else {it->second->teardown();m_sessions.erase(std::to_string(sessionId));response << "RTSP/1.0 200 OK\r\n";response << "CSeq: " << 1 << "\r\n";response << "Session: " << sessionId << "\r\n";response << "\r\n";}}else {response << "RTSP/1.0 501 Not Implemented\r\n";response << "CSeq: " << 1 << "\r\n";response << "\r\n";}// 发送响应std::string responseStr = response.str();send(clientSocket, responseStr.c_str(), responseStr.length(), 0);
}std::shared_ptr<RTSPSession> RTSPServer::createSession(const std::string& streamName) {auto it = m_mediaSources.find(streamName);if (it == m_mediaSources.end()) {return nullptr;}int sessionId = m_nextSessionId++;std::shared_ptr<RTSPSession> session = std::make_shared<RTSPSession>(sessionId, it->second);m_sessions[std::to_string(sessionId)] = session;return session;
}RTSPSession::RTSPSession(int sessionId, std::shared_ptr<MediaSource> mediaSource): m_sessionId(sessionId), m_mediaSource(mediaSource), m_clientRtpPort(0), m_clientRtcpPort(0), m_rtpSocket(-1), m_playing(false), m_senderRunning(false) {// 创建RTP套接字m_rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);if (m_rtpSocket == -1) {std::cerr << "Failed to create RTP socket" << std::endl;}
}RTSPSession::~RTSPSession() {teardown();if (m_rtpSocket != -1) {close(m_rtpSocket);}
}int RTSPSession::getSessionId() const {return m_sessionId;
}std::string RTSPSession::getSessionDescription() const {return m_mediaSource->getMediaDescription();
}void RTSPSession::setup(int clientRtpPort, int clientRtcpPort) {m_clientRtpPort = clientRtpPort;m_clientRtcpPort = clientRtcpPort;
}void RTSPSession::play() {if (m_playing) return;m_playing = true;m_senderRunning = true;m_senderThread = std::thread([this]() {sendRTPData();});
}void RTSPSession::pause() {m_playing = false;if (m_senderThread.joinable()) {m_senderThread.join();}
}void RTSPSession::teardown() {m_playing = false;m_senderRunning = false;if (m_senderThread.joinable()) {m_senderThread.join();}
}void RTSPSession::sendRTPData() {if (m_clientRtpPort == 0 || m_rtpSocket == -1) return;// 设置目标地址sockaddr_in destAddr;memset(&destAddr, 0, sizeof(destAddr));destAddr.sin_family = AF_INET;destAddr.sin_addr.s_addr = inet_addr("127.0.0.1");destAddr.sin_port = htons(m_clientRtpPort);// RTP包计数器和时间戳uint16_t sequenceNumber = 0;uint32_t timestamp = 0;uint32_t ssrc = rand();// 帧率控制uint32_t clockRate = m_mediaSource->getClockRate();uint32_t frameDuration = clockRate / 25; // 25fpswhile (m_senderRunning) {if (!m_playing) {std::this_thread::sleep_for(std::chrono::milliseconds(10));continue;}// 获取下一帧数据std::vector<uint8_t> frame;bool marker = false;m_mediaSource->getNextFrame(frame, timestamp, marker);if (frame.empty()) {std::this_thread::sleep_for(std::chrono::milliseconds(40));continue;}// 构建RTP头部const int RTP_HEADER_SIZE = 12;uint8_t rtpHeader[RTP_HEADER_SIZE];// 版本(2)、填充(0)、扩展(0)、CSRC计数(0)rtpHeader[0] = (2 << 6) | (0 << 5) | (0 << 4) | 0;// 标记位和负载类型(96 for H.264)rtpHeader[1] = (marker ? 0x80 : 0) | 96;// 序列号rtpHeader[2] = (sequenceNumber >> 8) & 0xFF;rtpHeader[3] = sequenceNumber & 0xFF;// 时间戳rtpHeader[4] = (timestamp >> 24) & 0xFF;rtpHeader[5] = (timestamp >> 16) & 0xFF;rtpHeader[6] = (timestamp >> 8) & 0xFF;rtpHeader[7] = timestamp & 0xFF;// SSRCrtpHeader[8] = (ssrc >> 24) & 0xFF;rtpHeader[9] = (ssrc >> 16) & 0xFF;rtpHeader[10] = (ssrc >> 8) & 0xFF;rtpHeader[11] = ssrc & 0xFF;// 发送RTP包std::vector<uint8_t> rtpPacket;rtpPacket.insert(rtpPacket.end(), rtpHeader, rtpHeader + RTP_HEADER_SIZE);rtpPacket.insert(rtpPacket.end(), frame.begin(), frame.end());sendto(m_rtpSocket, rtpPacket.data(), rtpPacket.size(), 0, (sockaddr*)&destAddr, sizeof(destAddr));// 更新序列号和时间戳sequenceNumber++;timestamp += frameDuration;// 控制帧率std::this_thread::sleep_for(std::chrono::milliseconds(40)); // 25fps}
}    

模拟生产frame视频帧
media_sources.h

#ifndef MEDIA_SOURCES_H
#define MEDIA_SOURCES_H#include "rtsp_server.h"
#include <random>class H264MediaSource : public MediaSource {
public:H264MediaSource(int width = 640, int height = 480);std::string getMimeType() const override;std::string getMediaDescription() const override;std::string getAttribute() const override;void getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) override;uint32_t getClockRate() const override;private:int m_width;int m_height;uint32_t m_currentTimestamp;std::mt19937 m_rng;
};class H265MediaSource : public MediaSource {
public:H265MediaSource(int width = 640, int height = 480);std::string getMimeType() const override;std::string getMediaDescription() const override;std::string getAttribute() const override;void getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) override;uint32_t getClockRate() const override;private:int m_width;int m_height;uint32_t m_currentTimestamp;std::mt19937 m_rng;
};class AACMediaSource : public MediaSource {
public:AACMediaSource(int sampleRate = 44100, int channels = 2);std::string getMimeType() const override;std::string getMediaDescription() const override;std::string getAttribute() const override;void getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) override;uint32_t getClockRate() const override;private:int m_sampleRate;int m_channels;uint32_t m_currentTimestamp;std::mt19937 m_rng;
};#endif // MEDIA_SOURCES_H    

media_sources.cpp

#include "media_sources.h"
#include <random>H264MediaSource::H264MediaSource(int width, int height): m_width(width), m_height(height), m_currentTimestamp(0) {std::random_device rd;m_rng = std::mt19937(rd());
}std::string H264MediaSource::getMimeType() const {return "H264";
}std::string H264MediaSource::getMediaDescription() const {return "H.264 Video Stream";
}std::string H264MediaSource::getAttribute() const {return "a=fmtp:96 packetization-mode=1;profile-level-id=42E01F;sprop-parameter-sets=Z0LAH9kA8AoAAAMAAgAAAwDAAAAwDxYuS,aM48gA==";
}void H264MediaSource::getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) {static bool isKeyFrame = true;static int frameCount = 0;// 每30帧生成一个关键帧if (frameCount++ % 30 == 0) {isKeyFrame = true;} else {isKeyFrame = false;}// 模拟H.264帧size_t frameSize = isKeyFrame ? 10000 + (m_rng() % 5000) : 2000 + (m_rng() % 3000);frame.resize(frameSize);// 模拟H.264 NAL单元头if (isKeyFrame) {// IDR帧frame[0] = 0x00;frame[1] = 0x00;frame[2] = 0x00;frame[3] = 0x01;frame[4] = 0x65; // IDR NAL单元类型} else {// P帧frame[0] = 0x00;frame[1] = 0x00;frame[2] = 0x00;frame[3] = 0x01;frame[4] = 0x41; // P NAL单元类型}// 填充随机数据for (size_t i = 5; i < frameSize; ++i) {frame[i] = m_rng() % 256;}timestamp = m_currentTimestamp;m_currentTimestamp += 90000 / 25; // 25fps,90kHz时钟marker = true; // 每帧都设置标记位
}uint32_t H264MediaSource::getClockRate() const {return 90000; // 90kHz
}H265MediaSource::H265MediaSource(int width, int height): m_width(width), m_height(height), m_currentTimestamp(0) {std::random_device rd;m_rng = std::mt19937(rd());
}std::string H265MediaSource::getMimeType() const {return "H265";
}std::string H265MediaSource::getMediaDescription() const {return "H.265 Video Stream";
}std::string H265MediaSource::getAttribute() const {return "a=fmtp:96 profile-id=1;level-id=110;packetization-mode=1";
}void H265MediaSource::getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) {static bool isKeyFrame = true;static int frameCount = 0;// 每30帧生成一个关键帧if (frameCount++ % 30 == 0) {isKeyFrame = true;} else {isKeyFrame = false;}// 模拟H.265帧size_t frameSize = isKeyFrame ? 12000 + (m_rng() % 6000) : 2500 + (m_rng() % 3500);frame.resize(frameSize);// 模拟H.265 NAL单元头if (isKeyFrame) {// IDR帧frame[0] = 0x00;frame[1] = 0x00;frame[2] = 0x00;frame[3] = 0x01;frame[4] = 0x40; // IDR_W_RADL NAL单元类型frame[5] = 0x01;} else {// P帧frame[0] = 0x00;frame[1] = 0x00;frame[2] = 0x00;frame[3] = 0x01;frame[4] = 0x02; // TRAIL_R NAL单元类型frame[5] = 0x01;}// 填充随机数据for (size_t i = 6; i < frameSize; ++i) {frame[i] = m_rng() % 256;}timestamp = m_currentTimestamp;m_currentTimestamp += 90000 / 25; // 25fps,90kHz时钟marker = true; // 每帧都设置标记位
}uint32_t H265MediaSource::getClockRate() const {return 90000; // 90kHz
}AACMediaSource::AACMediaSource(int sampleRate, int channels): m_sampleRate(sampleRate), m_channels(channels), m_currentTimestamp(0) {std::random_device rd;m_rng = std::mt19937(rd());
}std::string AACMediaSource::getMimeType() const {return "MPEG4-GENERIC";
}std::string AACMediaSource::getMediaDescription() const {return "AAC Audio Stream";
}std::string AACMediaSource::getAttribute() const {return "a=fmtp:96 streamtype=5;profile-level-id=15;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408";
}void AACMediaSource::getNextFrame(std::vector<uint8_t>& frame, uint32_t& timestamp, bool& marker) {// 模拟AAC帧size_t frameSize = 1024 + (m_rng() % 512); // 1024-1536字节frame.resize(frameSize);// 填充随机数据for (size_t i = 0; i < frameSize; ++i) {frame[i] = m_rng() % 256;}timestamp = m_currentTimestamp;m_currentTimestamp += 1024; // 每帧1024个样本marker = true;
}uint32_t AACMediaSource::getClockRate() const {return m_sampleRate;
}    

main.cpp

#include "rtsp_server.h"
#include "media_sources.h"
#include <iostream>
#include <memory>int main() {// 创建RTSP服务器,监听8554端口RTSPServer server(8554);// 创建并添加多路媒体源auto camera1_h264 = std::make_shared<H264MediaSource>(1280, 720);auto camera2_h264 = std::make_shared<H264MediaSource>(800, 600);auto camera3_h265 = std::make_shared<H265MediaSource>(1920, 1080);auto audio_aac = std::make_shared<AACMediaSource>(44100, 2);server.addMediaSource("camera1", camera1_h264);server.addMediaSource("camera2", camera2_h264);server.addMediaSource("camera3", camera3_h265);server.addMediaSource("audio", audio_aac);// 启动服务器if (server.start()) {std::cout << "RTSP Server started on port 8554" << std::endl;std::cout << "Available streams:" << std::endl;std::cout << "rtsp://localhost:8554/camera1 (H.264 1280x720)" << std::endl;std::cout << "rtsp://localhost:8554/camera2 (H.264 800x600)" << std::endl;std::cout << "rtsp://localhost:8554/camera3 (H.265 1920x1080)" << std::endl;std::cout << "rtsp://localhost:8554/audio (AAC 44.1kHz stereo)" << std::endl;// 保持服务器运行std::cout << "Press Enter to stop the server..." << std::endl;std::cin.get();} else {std::cerr << "Failed to start RTSP server" << std::endl;}// 停止服务器server.stop();return 0;
}    

rtsp_client.h

#ifndef RTSP_CLIENT_H
#define RTSP_CLIENT_H#include <string>
#include <memory>
#include <thread>
#include <mutex>
#include <vector>
#include <functional>
#include <cstdint>// RTP包结构
struct RTPPacket {uint8_t version;      // 版本号uint8_t padding;      // 填充标志uint8_t extension;    // 扩展标志uint8_t cc;           // CSRC计数器uint8_t marker;       // 标记位uint8_t payloadType;  // 负载类型uint16_t sequence;    // 序列号uint32_t timestamp;   // 时间戳uint32_t ssrc;        // 同步源标识符std::vector<uint8_t> payload; // 负载数据
};// RTSP客户端类
class RTSPClient {
public:// 帧数据回调函数类型using FrameCallback = std::function<void(const uint8_t*, size_t, uint32_t, bool)>;RTSPClient();~RTSPClient();// 连接到RTSP服务器bool connect(const std::string& rtspUrl);// 断开连接void disconnect();// 设置帧数据回调void setFrameCallback(const FrameCallback& callback);// 开始播放bool play();// 暂停播放bool pause();// 获取媒体信息std::string getMediaInfo() const;private:// 状态枚举enum class State {INIT,READY,PLAYING,PAUSED,TEARDOWN};// 解析RTSP URLbool parseUrl(const std::string& rtspUrl);// 发送RTSP请求std::string sendRequest(const std::string& request);// 接收RTP/RTCP数据void receiveRTPData();// 解析SDP信息bool parseSDP(const std::string& sdp);// 解析RTP包bool parseRTPPacket(const std::vector<uint8_t>& data, RTPPacket& packet);// 处理视频帧void processVideoFrame(const RTPPacket& packet);// 处理音频帧void processAudioFrame(const RTPPacket& packet);// 成员变量std::string m_serverAddress;int m_serverPort;std::string m_streamPath;int m_rtspSocket;std::string m_sessionId;State m_state;int m_cseq;int m_rtpPort;int m_rtcpPort;std::thread m_receiveThread;bool m_running;mutable std::mutex m_mutex;FrameCallback m_frameCallback;std::string m_mediaInfo;std::string m_codec;int m_clockRate;
};#endif // RTSP_CLIENT_H    

rtsp_client.cpp

#include "rtsp_client.h"
#include <iostream>
#include <sstream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <regex>RTSPClient::RTSPClient(): m_serverPort(554), m_rtspSocket(-1), m_state(State::INIT), m_cseq(0), m_rtpPort(0), m_rtcpPort(0), m_running(false), m_clockRate(90000) {
}RTSPClient::~RTSPClient() {disconnect();
}bool RTSPClient::connect(const std::string& rtspUrl) {std::lock_guard<std::mutex> lock(m_mutex);if (m_state != State::INIT) {std::cerr << "RTSPClient: Already connected or in progress" << std::endl;return false;}if (!parseUrl(rtspUrl)) {std::cerr << "RTSPClient: Failed to parse URL: " << rtspUrl << std::endl;return false;}// 创建RTSP套接字m_rtspSocket = socket(AF_INET, SOCK_STREAM, 0);if (m_rtspSocket < 0) {std::cerr << "RTSPClient: Failed to create socket" << std::endl;return false;}// 设置服务器地址sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(m_serverPort);// 解析服务器地址if (inet_pton(AF_INET, m_serverAddress.c_str(), &serverAddr.sin_addr) <= 0) {std::cerr << "RTSPClient: Invalid address/ Address not supported" << std::endl;close(m_rtspSocket);m_rtspSocket = -1;return false;}// 连接服务器if (connect(m_rtspSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {std::cerr << "RTSPClient: Connection failed" << std::endl;close(m_rtspSocket);m_rtspSocket = -1;return false;}// 发送OPTIONS请求std::string request = "OPTIONS " + rtspUrl + " RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";request += "User-Agent: RTSPClient\r\n\r\n";std::string response = sendRequest(request);if (response.empty()) {std::cerr << "RTSPClient: No response to OPTIONS request" << std::endl;disconnect();return false;}// 发送DESCRIBE请求request = "DESCRIBE " + rtspUrl + " RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";request += "Accept: application/sdp\r\n";request += "User-Agent: RTSPClient\r\n\r\n";response = sendRequest(request);if (response.empty()) {std::cerr << "RTSPClient: No response to DESCRIBE request" << std::endl;disconnect();return false;}// 解析SDP信息if (!parseSDP(response)) {std::cerr << "RTSPClient: Failed to parse SDP" << std::endl;disconnect();return false;}m_state = State::READY;return true;
}void RTSPClient::disconnect() {std::lock_guard<std::mutex> lock(m_mutex);if (m_state == State::TEARDOWN) {return;}// 发送TEARDOWN请求if (m_state != State::INIT && m_rtspSocket != -1) {std::string request = "TEARDOWN " + m_streamPath + " RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";if (!m_sessionId.empty()) {request += "Session: " + m_sessionId + "\r\n";}request += "User-Agent: RTSPClient\r\n\r\n";sendRequest(request);}m_state = State::TEARDOWN;// 停止接收线程m_running = false;if (m_receiveThread.joinable()) {m_receiveThread.join();}// 关闭套接字if (m_rtspSocket != -1) {close(m_rtspSocket);m_rtspSocket = -1;}
}bool RTSPClient::play() {std::lock_guard<std::mutex> lock(m_mutex);if (m_state != State::READY && m_state != State::PAUSED) {std::cerr << "RTSPClient: Cannot play in current state" << std::endl;return false;}// 发送SETUP请求std::string request = "SETUP " + m_streamPath + "/track1 RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";if (!m_sessionId.empty()) {request += "Session: " + m_sessionId + "\r\n";}request += "Transport: RTP/AVP;unicast;client_port=" + std::to_string(m_rtpPort) + "-" + std::to_string(m_rtcpPort) + "\r\n";request += "User-Agent: RTSPClient\r\n\r\n";std::string response = sendRequest(request);if (response.empty()) {std::cerr << "RTSPClient: No response to SETUP request" << std::endl;return false;}// 提取会话IDstd::regex sessionRegex("Session: (.*?)\r\n");std::smatch sessionMatch;if (std::regex_search(response, sessionMatch, sessionRegex) && sessionMatch.size() > 1) {m_sessionId = sessionMatch[1].str();// 去除可能的超时参数size_t semicolonPos = m_sessionId.find(';');if (semicolonPos != std::string::npos) {m_sessionId = m_sessionId.substr(0, semicolonPos);}} else {std::cerr << "RTSPClient: Session ID not found in SETUP response" << std::endl;return false;}// 提取服务器端口std::regex serverPortRegex("server_port=(\\d+)-(\\d+)");std::smatch portMatch;if (std::regex_search(response, portMatch, serverPortRegex) && portMatch.size() > 2) {// 这里可以获取服务器的RTP和RTCP端口,但实际使用中我们通常只需要客户端端口}// 发送PLAY请求request = "PLAY " + m_streamPath + " RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";request += "Session: " + m_sessionId + "\r\n";request += "User-Agent: RTSPClient\r\n\r\n";response = sendRequest(request);if (response.empty()) {std::cerr << "RTSPClient: No response to PLAY request" << std::endl;return false;}// 启动接收线程m_running = true;m_receiveThread = std::thread(&RTSPClient::receiveRTPData, this);m_state = State::PLAYING;return true;
}bool RTSPClient::pause() {std::lock_guard<std::mutex> lock(m_mutex);if (m_state != State::PLAYING) {std::cerr << "RTSPClient: Cannot pause in current state" << std::endl;return false;}// 发送PAUSE请求std::string request = "PAUSE " + m_streamPath + " RTSP/1.0\r\n";request += "CSeq: " + std::to_string(++m_cseq) + "\r\n";request += "Session: " + m_sessionId + "\r\n";request += "User-Agent: RTSPClient\r\n\r\n";std::string response = sendRequest(request);if (response.empty()) {std::cerr << "RTSPClient: No response to PAUSE request" << std::endl;return false;}m_state = State::PAUSED;return true;
}std::string RTSPClient::getMediaInfo() const {std::lock_guard<std::mutex> lock(m_mutex);return m_mediaInfo;
}void RTSPClient::setFrameCallback(const FrameCallback& callback) {std::lock_guard<std::mutex> lock(m_mutex);m_frameCallback = callback;
}bool RTSPClient::parseUrl(const std::string& rtspUrl) {// 解析RTSP URL: rtsp://server:port/pathstd::regex urlRegex("rtsp://([^:/]+)(?::(\\d+))?(/.*)");std::smatch match;if (std::regex_match(rtspUrl, match, urlRegex) && match.size() >= 3) {m_serverAddress = match[1].str();if (match.length(2) > 0) {m_serverPort = std::stoi(match[2].str());}m_streamPath = "rtsp://" + m_serverAddress + (match.length(2) > 0 ? (":" + match[2].str()) : "") + match[3].str();return true;}return false;
}std::string RTSPClient::sendRequest(const std::string& request) {if (m_rtspSocket == -1) {return "";}// 发送请求ssize_t sent = send(m_rtspSocket, request.c_str(), request.length(), 0);if (sent < 0) {std::cerr << "RTSPClient: Error sending request" << std::endl;return "";}// 接收响应char buffer[4096];std::string response;while (true) {memset(buffer, 0, sizeof(buffer));ssize_t recvSize = recv(m_rtspSocket, buffer, sizeof(buffer) - 1, 0);if (recvSize <= 0) {break;}response.append(buffer, recvSize);// 检查是否接收到完整的响应if (response.find("\r\n\r\n") != std::string::npos) {break;}}return response;
}bool RTSPClient::parseSDP(const std::string& sdp) {// 查找SDP部分size_t sdpStart = sdp.find("\r\n\r\n");if (sdpStart == std::string::npos) {return false;}std::string sdpContent = sdp.substr(sdpStart + 4);// 解析媒体信息std::regex mediaRegex("m=([^ ]+) ([^ ]+) ([^ ]+) ([^\r\n]+)");std::sregex_iterator currentMatch(sdpContent.begin(), sdpContent.end(), mediaRegex);std::sregex_iterator end;bool foundVideo = false;bool foundAudio = false;while (currentMatch != end) {std::smatch match = *currentMatch;std::string mediaType = match[1].str();std::string port = match[2].str();std::string proto = match[3].str();std::string fmt = match[4].str();if (mediaType == "video" && !foundVideo) {foundVideo = true;m_mediaInfo += "Video: " + fmt + "\n";// 查找编码信息std::regex codecRegex("a=rtpmap:(\\d+) (H264|H265|MP4V-ES)(?:/([^/\\r\\n]+))?");std::smatch codecMatch;if (std::regex_search(sdpContent, codecMatch, codecRegex) && codecMatch.size() >= 3) {m_codec = codecMatch[2].str();if (codecMatch.size() > 3 && codecMatch.length(3) > 0) {m_clockRate = std::stoi(codecMatch[3].str());}}} else if (mediaType == "audio" && !foundAudio) {foundAudio = true;m_mediaInfo += "Audio: " + fmt + "\n";// 查找编码信息std::regex codecRegex("a=rtpmap:(\\d+) (MPEG4-GENERIC|PCMA|PCMU)(?:/([^/\\r\\n]+))?");std::smatch codecMatch;if (std::regex_search(sdpContent, codecMatch, codecRegex) && codecMatch.size() >= 3) {if (codecMatch[2].str() == "MPEG4-GENERIC") {m_codec = "AAC";} else {m_codec = codecMatch[2].str();}if (codecMatch.size() > 3 && codecMatch.length(3) > 0) {m_clockRate = std::stoi(codecMatch[3].str());}}}++currentMatch;}// 为RTP和RTCP选择随机端口m_rtpPort = 10000 + (rand() % 5000) * 2;m_rtcpPort = m_rtpPort + 1;return true;
}void RTSPClient::receiveRTPData() {// 创建RTP套接字int rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);if (rtpSocket < 0) {std::cerr << "RTSPClient: Failed to create RTP socket" << std::endl;return;}// 绑定RTP端口sockaddr_in rtpAddr;memset(&rtpAddr, 0, sizeof(rtpAddr));rtpAddr.sin_family = AF_INET;rtpAddr.sin_addr.s_addr = INADDR_ANY;rtpAddr.sin_port = htons(m_rtpPort);if (bind(rtpSocket, (struct sockaddr*)&rtpAddr, sizeof(rtpAddr)) < 0) {std::cerr << "RTSPClient: Failed to bind RTP socket" << std::endl;close(rtpSocket);return;}// 设置非阻塞模式// int flags = fcntl(rtpSocket, F_GETFL, 0);// fcntl(rtpSocket, F_SETFL, flags | O_NONBLOCK);char buffer[16000];sockaddr_in senderAddr;socklen_t senderAddrLen = sizeof(senderAddr);std::vector<uint8_t> frameData;uint32_t lastTimestamp = 0;bool isKeyFrame = false;while (m_running) {memset(buffer, 0, sizeof(buffer));ssize_t recvSize = recvfrom(rtpSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&senderAddr, &senderAddrLen);if (recvSize > 0) {std::vector<uint8_t> packetData(buffer, buffer + recvSize);RTPPacket packet;if (parseRTPPacket(packetData, packet)) {// 处理视频帧if (m_codec == "H264" || m_codec == "H265") {processVideoFrame(packet);} // 处理音频帧else if (m_codec == "AAC") {processAudioFrame(packet);}}} else {// 超时或错误,短暂休眠std::this_thread::sleep_for(std::chrono::milliseconds(10));}}close(rtpSocket);
}bool RTSPClient::parseRTPPacket(const std::vector<uint8_t>& data, RTPPacket& packet) {if (data.size() < 12) {return false;}// 解析RTP头部packet.version = (data[0] >> 6) & 0x03;packet.padding = (data[0] >> 5) & 0x01;packet.extension = (data[0] >> 4) & 0x01;packet.cc = data[0] & 0x0F;packet.marker = (data[1] >> 7) & 0x01;packet.payloadType = data[1] & 0x7F;packet.sequence = (data[2] << 8) | data[3];packet.timestamp = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];packet.ssrc = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11];// 提取负载数据size_t headerSize = 12 + (packet.cc * 4);if (packet.extension && (data.size() > headerSize + 4)) {uint16_t extLength = (data[headerSize + 2] << 8) | data[headerSize + 3];headerSize += 4 + (extLength * 4);}if (data.size() > headerSize) {packet.payload.assign(data.begin() + headerSize, data.end());return true;}return false;
}void RTSPClient::processVideoFrame(const RTPPacket& packet) {std::lock_guard<std::mutex> lock(m_mutex);if (!m_frameCallback) {return;}if (m_codec == "H264" && packet.payload.size() > 0) {// H.264 NAL单元类型uint8_t nalType = packet.payload[0] & 0x1F;// 判断是否为关键帧bool isKeyFrame = (nalType == 7) || (nalType == 5);// 调用回调函数m_frameCallback(packet.payload.data(), packet.payload.size(), packet.timestamp, isKeyFrame);} else if (m_codec == "H265" && packet.payload.size() > 0) {// H.265 NAL单元类型uint8_t nalType = (packet.payload[0] >> 1) & 0x3F;// 判断是否为关键帧bool isKeyFrame = (nalType >= 16 && nalType <= 23);// 调用回调函数m_frameCallback(packet.payload.data(), packet.payload.size(), packet.timestamp, isKeyFrame);}
}void RTSPClient::processAudioFrame(const RTPPacket& packet) {std::lock_guard<std::mutex> lock(m_mutex);if (!m_frameCallback) {return;}// 对于AAC,每个RTP包通常包含一个完整的ADTS帧m_frameCallback(packet.payload.data(), packet.payload.size(), packet.timestamp, false);
}    

client_example.cpp

#include "rtsp_client.h"
#include <iostream>
#include <fstream>
#include <atomic>
#include <thread>// 简单的帧处理器
class FrameProcessor {
public:FrameProcessor(const std::string& outputPath) : m_outputPath(outputPath), m_frameCount(0) {}void handleFrame(const uint8_t* data, size_t size, uint32_t timestamp, bool isKeyFrame) {std::cout << "Frame received: size=" << size << ", timestamp=" << timestamp << ", keyframe=" << (isKeyFrame ? "YES" : "NO") << std::endl;// 保存帧数据到文件if (m_outputPath.empty()) {return;}std::stringstream ss;ss << m_outputPath << "/frame_" << m_frameCount++ << ".h264";std::ofstream frameFile(ss.str(), std::ios::binary);if (frameFile.is_open()) {frameFile.write(reinterpret_cast<const char*>(data), size);frameFile.close();}}private:std::string m_outputPath;std::atomic<int> m_frameCount;
};int main(int argc, char* argv[]) {if (argc < 2) {std::cout << "Usage: " << argv[0] << " <rtsp_url> [output_dir]" << std::endl;return 1;}std::string rtspUrl = argv[1];std::string outputPath = (argc > 2) ? argv[2] : "";// 创建帧处理器FrameProcessor processor(outputPath);// 创建RTSP客户端RTSPClient client;// 设置帧回调client.setFrameCallback([&processor](const uint8_t* data, size_t size, uint32_t timestamp, bool isKeyFrame) {processor.handleFrame(data, size, timestamp, isKeyFrame);});// 连接到RTSP服务器if (!client.connect(rtspUrl)) {std::cerr << "Failed to connect to RTSP server" << std::endl;return 1;}// 打印媒体信息std::cout << "Media information:" << std::endl;std::cout << client.getMediaInfo() << std::endl;// 开始播放if (!client.play()) {std::cerr << "Failed to start playing" << std::endl;client.disconnect();return 1;}std::cout << "Playing stream from: " << rtspUrl << std::endl;std::cout << "Press Enter to stop..." << std::endl;// 等待用户输入std::cin.get();// 停止播放client.disconnect();std::cout << "RTSP client stopped" << std::endl;return 0;
}    

2、完整一点的判断帧类型,分包,解包

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <cstring>
#include <thread>
#include <atomic>
#include <random>
#include <ctime>// RTSP协议常量
const int RTSP_PORT = 8554;
const int RTP_PORT_BASE = 5000;
const int RTCP_PORT_BASE = 5001;
const int MAX_PACKET_SIZE = 1400;  // MTU - IP头 - UDP头// NAL单元类型
enum NalUnitType {NAL_UNKNOWN = 0,NAL_SLICE = 1,NAL_IDR_SLICE = 5,NAL_SEI = 6,NAL_SPS = 7,NAL_PPS = 8,NAL_AUD = 9
};// 帧类型
enum FrameType {FRAME_I = 0,FRAME_P = 1,FRAME_B = 2
};// RTP包结构
struct RTPPacket {uint8_t version;      // 版本号(2 bits)uint8_t padding;      // 填充标志(1 bit)uint8_t extension;    // 扩展标志(1 bit)uint8_t cc;           // CSRC计数器(4 bits)uint8_t marker;       // 标记位(1 bit)uint8_t payloadType;  // 负载类型(7 bits)uint16_t sequence;    // 序列号uint32_t timestamp;   // 时间戳uint32_t ssrc;        // 同步源标识符std::vector<uint8_t> payload;  // 负载数据RTPPacket() : version(2), padding(0), extension(0), cc(0), marker(0), payloadType(96), sequence(0), timestamp(0), ssrc(0) {}
};// 会话信息
struct Session {std::string sessionId;std::string streamName;int clientRtpPort;int clientRtcpPort;int serverRtpPort;int serverRtcpPort;std::atomic<bool> isPlaying;std::thread streamThread;Session() : clientRtpPort(0), clientRtcpPort(0), serverRtpPort(0), serverRtcpPort(0), isPlaying(false) {}
};// 媒体源接口
class MediaSource {
public:virtual ~MediaSource() {}virtual std::string getMimeType() = 0;virtual std::string getSdpDescription() = 0;virtual bool getNextFrame(std::vector<uint8_t>& frameData, FrameType& frameType) = 0;
};// H.264媒体源实现
class H264MediaSource : public MediaSource {
private:int width;int height;int fps;uint32_t frameCount;std::mt19937 randomGenerator;std::uniform_int_distribution<int> frameTypeDist;public:H264MediaSource(int w, int h, int f = 30) : width(w), height(h), fps(f), frameCount(0) {randomGenerator.seed(std::time(nullptr));frameTypeDist = std::uniform_int_distribution<int>(0, 9);  // 0-9的随机数}std::string getMimeType() override {return "video/H264";}std::string getSdpDescription() override {std::string sdp = "m=video 0 RTP/AVP 96\r\n""a=rtpmap:96 H264/90000\r\n""a=fmtp:96 packetization-mode=1;profile-level-id=42001F;sprop-parameter-sets=Z0IAKeKQDwBE/LwEBAAMAAgAAAwAymA0A,aM48gA==\r\n""a=control:streamid=0\r\n";return sdp;}bool getNextFrame(std::vector<uint8_t>& frameData, FrameType& frameType) override {// 模拟生成H.264帧frameCount++;// 每30帧生成一个I帧,其余为P帧(90%)或B帧(10%)if (frameCount % 30 == 0) {frameType = FRAME_I;generateIFrame(frameData);} else {int randVal = frameTypeDist(randomGenerator);if (randVal < 9) {frameType = FRAME_P;generatePFrame(frameData);} else {frameType = FRAME_B;generateBFrame(frameData);}}return true;}private:void generateIFrame(std::vector<uint8_t>& frameData) {// 模拟生成I帧 (SPS + PPS + IDR)// SPS (Sequence Parameter Set)std::vector<uint8_t> sps = {0x67, 0x42, 0x00, 0x1F, 0xE9, 0x01, 0x40, 0x16, 0xE3, 0x88, 0x80, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0C, 0x8C, 0x3C, 0x60};// PPS (Picture Parameter Set)std::vector<uint8_t> pps = {0x68, 0xCE, 0x38, 0x80};// IDR Slice (随机数据模拟)std::vector<uint8_t> idr(1024 * 4);  // 4KB的IDR图像idr[0] = 0x65;  // NAL单元类型: IDR Slicefor (size_t i = 1; i < idr.size(); i++) {idr[i] = rand() % 256;}// 组合成完整的I帧frameData.insert(frameData.end(), sps.begin(), sps.end());frameData.insert(frameData.end(), pps.begin(), pps.end());frameData.insert(frameData.end(), idr.begin(), idr.end());}void generatePFrame(std::vector<uint8_t>& frameData) {// 模拟生成P帧frameData.resize(1024 * 2);  // 2KB的P帧frameData[0] = 0x41;  // NAL单元类型: 非IDR Slicefor (size_t i = 1; i < frameData.size(); i++) {frameData[i] = rand() % 256;}}void generateBFrame(std::vector<uint8_t>& frameData) {// 模拟生成B帧frameData.resize(1024);  // 1KB的B帧frameData[0] = 0x41;  // NAL单元类型: 非IDR Slice (与P帧相同)for (size_t i = 1; i < frameData.size(); i++) {frameData[i] = rand() % 256;}}
};// AAC媒体源实现
class AACMediaSource : public MediaSource {
private:int sampleRate;int channels;public:AACMediaSource(int sr = 44100, int ch = 2) : sampleRate(sr), channels(ch) {}std::string getMimeType() override {return "audio/AAC";}std::string getSdpDescription() override {std::string sdp = "m=audio 0 RTP/AVP 97\r\n""a=rtpmap:97 MPEG4-GENERIC/" + std::to_string(sampleRate) + "/" + std::to_string(channels) + "\r\n""a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1210\r\n""a=control:streamid=1\r\n";return sdp;}bool getNextFrame(std::vector<uint8_t>& frameData, FrameType& frameType) override {// 模拟生成AAC音频帧frameData.resize(1024);  // 1KB的AAC帧for (size_t i = 0; i < frameData.size(); i++) {frameData[i] = rand() % 256;}return true;}
};// RTSP服务器
class RTSPServer {
private:int serverSocket;std::map<std::string, std::shared_ptr<MediaSource>> mediaSources;std::map<std::string, Session> sessions;std::atomic<bool> running;std::thread acceptThread;public:RTSPServer(int port = RTSP_PORT) : serverSocket(-1), running(false) {// 创建TCP套接字serverSocket = socket(AF_INET, SOCK_STREAM, 0);if (serverSocket < 0) {std::cerr << "Failed to create socket" << std::endl;return;}// 设置套接字选项int opt = 1;if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {std::cerr << "Failed to set socket options" << std::endl;close(serverSocket);serverSocket = -1;return;}// 绑定地址sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = INADDR_ANY;serverAddr.sin_port = htons(port);if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {std::cerr << "Failed to bind socket" << std::endl;close(serverSocket);serverSocket = -1;return;}// 监听连接if (listen(serverSocket, 5) < 0) {std::cerr << "Failed to listen on socket" << std::endl;close(serverSocket);serverSocket = -1;return;}}~RTSPServer() {stop();if (serverSocket >= 0) {close(serverSocket);}}void addMediaSource(const std::string& name, std::shared_ptr<MediaSource> source) {mediaSources[name] = source;}bool start() {if (serverSocket < 0 || running) {return false;}running = true;acceptThread = std::thread(&RTSPServer::acceptConnections, this);return true;}void stop() {running = false;if (acceptThread.joinable()) {acceptThread.join();}// 停止所有会话for (auto& pair : sessions) {pair.second.isPlaying = false;if (pair.second.streamThread.joinable()) {pair.second.streamThread.join();}}sessions.clear();}private:void acceptConnections() {while (running) {sockaddr_in clientAddr;socklen_t clientAddrLen = sizeof(clientAddr);int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);if (clientSocket < 0) {if (running) {std::cerr << "Failed to accept client connection" << std::endl;}continue;}// 为客户端创建处理线程std::thread([this, clientSocket]() {handleClient(clientSocket);close(clientSocket);}).detach();}}void handleClient(int clientSocket) {char buffer[4096];std::string sessionId;while (running) {memset(buffer, 0, sizeof(buffer));int bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);if (bytesRead <= 0) {break;  // 客户端断开连接}std::string request(buffer, bytesRead);std::string response = processRequest(request, sessionId);send(clientSocket, response.c_str(), response.size(), 0);}}std::string processRequest(const std::string& request, std::string& sessionId) {// 解析RTSP请求方法size_t methodEnd = request.find(' ');if (methodEnd == std::string::npos) {return "RTSP/1.0 400 Bad Request\r\n\r\n";}std::string method = request.substr(0, methodEnd);size_t uriEnd = request.find(' ', methodEnd + 1);if (uriEnd == std::string::npos) {return "RTSP/1.0 400 Bad Request\r\n\r\n";}std::string uri = request.substr(methodEnd + 1, uriEnd - methodEnd - 1);size_t seqStart = request.find("CSeq: ") + 6;size_t seqEnd = request.find("\r\n", seqStart);std::string cseq = request.substr(seqStart, seqEnd - seqStart);// 处理不同的RTSP方法if (method == "OPTIONS") {return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Public: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE, TEARDOWN\r\n\r\n";}else if (method == "DESCRIBE") {// 提取流名称size_t streamStart = uri.find_last_of('/') + 1;std::string streamName = uri.substr(streamStart);auto it = mediaSources.find(streamName);if (it == mediaSources.end()) {return "RTSP/1.0 404 Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}std::string sdp = it->second->getSdpDescription();return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Content-Base: " + uri + "/\r\n""Content-Type: application/sdp\r\n""Content-Length: " + std::to_string(sdp.size()) + "\r\n\r\n"+ sdp;}else if (method == "SETUP") {// 提取流名称size_t streamStart = uri.find_last_of('/') + 1;std::string streamName = uri.substr(streamStart);auto it = mediaSources.find(streamName);if (it == mediaSources.end()) {return "RTSP/1.0 404 Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}// 提取客户端端口size_t transportStart = request.find("Transport: ") + 11;size_t transportEnd = request.find("\r\n", transportStart);std::string transport = request.substr(transportStart, transportEnd - transportStart);size_t clientPortStart = transport.find("client_port=") + 12;size_t clientPortEnd = transport.find("-", clientPortStart);int clientRtpPort = std::stoi(transport.substr(clientPortStart, clientPortEnd - clientPortStart));int clientRtcpPort = std::stoi(transport.substr(clientPortEnd + 1));// 生成会话IDsessionId = generateSessionId();// 创建会话Session session;session.sessionId = sessionId;session.streamName = streamName;session.clientRtpPort = clientRtpPort;session.clientRtcpPort = clientRtcpPort;session.serverRtpPort = RTP_PORT_BASE + (sessions.size() * 2);session.serverRtcpPort = RTCP_PORT_BASE + (sessions.size() * 2);session.isPlaying = false;sessions[sessionId] = session;return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Session: " + sessionId + "\r\n""Transport: RTP/AVP;unicast;client_port=" + std::to_string(clientRtpPort) + "-" + std::to_string(clientRtcpPort) + ";server_port=" + std::to_string(session.serverRtpPort) + "-" + std::to_string(session.serverRtcpPort) + "\r\n\r\n";}else if (method == "PLAY") {if (sessionId.empty()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}auto it = sessions.find(sessionId);if (it == sessions.end()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}// 启动流发送线程if (!it->second.isPlaying) {it->second.isPlaying = true;it->second.streamThread = std::thread(&RTSPServer::sendMediaStream, this, sessionId);}return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Session: " + sessionId + "\r\n""RTP-Info: url=" + uri + "\r\n\r\n";}else if (method == "PAUSE") {if (sessionId.empty()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}auto it = sessions.find(sessionId);if (it == sessions.end()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}it->second.isPlaying = false;if (it->second.streamThread.joinable()) {it->second.streamThread.join();}return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Session: " + sessionId + "\r\n\r\n";}else if (method == "TEARDOWN") {if (sessionId.empty()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}auto it = sessions.find(sessionId);if (it == sessions.end()) {return "RTSP/1.0 454 Session Not Found\r\n""CSeq: " + cseq + "\r\n\r\n";}it->second.isPlaying = false;if (it->second.streamThread.joinable()) {it->second.streamThread.join();}sessions.erase(sessionId);sessionId.clear();return "RTSP/1.0 200 OK\r\n""CSeq: " + cseq + "\r\n""Session: " + sessionId + "\r\n\r\n";}else {return "RTSP/1.0 501 Not Implemented\r\n""CSeq: " + cseq + "\r\n\r\n";}}std::string generateSessionId() {static std::atomic<uint32_t> sessionCounter(0);return std::to_string(time(nullptr)) + "_" + std::to_string(sessionCounter++);}void sendMediaStream(const std::string& sessionId) {auto it = sessions.find(sessionId);if (it == sessions.end()) {return;}Session& session = it->second;auto mediaSourceIt = mediaSources.find(session.streamName);if (mediaSourceIt == mediaSources.end()) {return;}std::shared_ptr<MediaSource> mediaSource = mediaSourceIt->second;// 创建RTP套接字int rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);if (rtpSocket < 0) {std::cerr << "Failed to create RTP socket" << std::endl;return;}// 设置目标地址sockaddr_in clientAddr;memset(&clientAddr, 0, sizeof(clientAddr));clientAddr.sin_family = AF_INET;clientAddr.sin_addr.s_addr = INADDR_ANY;  // 注意:实际应用中应使用客户端IPclientAddr.sin_port = htons(session.clientRtpPort);// 初始化RTP参数RTPPacket rtpPacket;rtpPacket.ssrc = rand();rtpPacket.sequence = rand() % 65536;rtpPacket.timestamp = rand() % 4294967296;// 帧率控制const int fps = 30;const int frameDuration = 1000 / fps;  // 毫秒uint32_t frameCount = 0;while (session.isPlaying) {std::vector<uint8_t> frameData;FrameType frameType;// 获取下一帧if (!mediaSource->getNextFrame(frameData, frameType)) {break;}// 分片并发送if (mediaSource->getMimeType() == "video/H264") {sendH264Frame(rtpSocket, clientAddr, frameData, frameType, rtpPacket);} else {// 其他媒体类型的处理sendRtpPacket(rtpSocket, clientAddr, frameData, rtpPacket);}// 更新时间戳和序列号rtpPacket.sequence = (rtpPacket.sequence + 1) % 65536;rtpPacket.timestamp += 90000 / fps;  // 90kHz时钟frameCount++;// 控制帧率std::this_thread::sleep_for(std::chrono::milliseconds(frameDuration));}close(rtpSocket);}void sendH264Frame(int socket, sockaddr_in& addr, const std::vector<uint8_t>& frameData, FrameType frameType, RTPPacket& rtpPacket) {// 查找帧中的各个NAL单元size_t offset = 0;while (offset < frameData.size()) {// 查找NAL单元起始码 (0x00000001或0x000001)size_t nalStart = offset;if (frameData.size() - offset >= 4 && frameData[offset] == 0x00 && frameData[offset+1] == 0x00 && frameData[offset+2] == 0x00 && frameData[offset+3] == 0x01) {nalStart += 4;offset += 4;} else if (frameData.size() - offset >= 3 && frameData[offset] == 0x00 && frameData[offset+1] == 0x00 && frameData[offset+2] == 0x01) {nalStart += 3;offset += 3;} else {// 没有找到起始码,可能是第一个NALnalStart = offset;}// 查找下一个NAL单元的起始位置size_t nextNalStart = frameData.size();for (size_t i = offset; i < frameData.size() - 3; i++) {if ((frameData[i] == 0x00 && frameData[i+1] == 0x00 && frameData[i+2] == 0x00 && frameData[i+3] == 0x01) ||(frameData[i] == 0x00 && frameData[i+1] == 0x00 && frameData[i+2] == 0x01)) {nextNalStart = i;break;}}size_t nalSize = nextNalStart - nalStart;if (nalSize <= 0) {break;}// 处理NAL单元const uint8_t* nalData = &frameData[nalStart];uint8_t nalType = nalData[0] & 0x1F;// 根据NAL单元类型设置RTP标记位bool isKeyFrame = (nalType == NAL_IDR_SLICE || nalType == NAL_SPS || nalType == NAL_PPS);rtpPacket.marker = isKeyFrame ? 1 : 0;// 分片处理if (nalSize > MAX_PACKET_SIZE) {fragmentAndSendNAL(socket, addr, nalData, nalSize, rtpPacket);} else {// 不需要分片,直接发送rtpPacket.payload.assign(nalData, nalData + nalSize);sendRtpPacket(socket, addr, rtpPacket);rtpPacket.sequence = (rtpPacket.sequence + 1) % 65536;}offset = nextNalStart;}}void fragmentAndSendNAL(int socket, sockaddr_in& addr, const uint8_t* nalData, size_t nalSize, RTPPacket& rtpPacket) {uint8_t originalNalType = nalData[0] & 0x1F;uint8_t fuIndicator = 0x7C;  // FU-A indicator (Type=28)size_t payloadSize = MAX_PACKET_SIZE - 2;  // 减去FU indicator和FU header的大小size_t offset = 1;  // 跳过原始NAL头部while (offset < nalSize) {size_t fragmentSize = std::min(payloadSize, nalSize - offset);bool isFirst = (offset == 1);bool isLast = (offset + fragmentSize >= nalSize);// 创建FU headeruint8_t fuHeader = originalNalType;if (isFirst) fuHeader |= 0x80;  // 设置起始位if (isLast) fuHeader |= 0x40;   // 设置结束位// 构建RTP负载rtpPacket.payload.resize(2 + fragmentSize);rtpPacket.payload[0] = fuIndicator;rtpPacket.payload[1] = fuHeader;memcpy(&rtpPacket.payload[2], nalData + offset, fragmentSize);// 设置标记位(仅最后一个分片设置)rtpPacket.marker = isLast ? 1 : 0;// 发送RTP包sendRtpPacket(socket, addr, rtpPacket);// 更新序列号rtpPacket.sequence = (rtpPacket.sequence + 1) % 65536;offset += fragmentSize;}}void sendRtpPacket(int socket, sockaddr_in& addr, const std::vector<uint8_t>& payload, RTPPacket& rtpPacket) {// 构建RTP头部uint8_t rtpHeader[12];rtpHeader[0] = (rtpPacket.version << 6) | (rtpPacket.padding << 5) | (rtpPacket.extension << 4) | rtpPacket.cc;rtpHeader[1] = (rtpPacket.marker << 7) | rtpPacket.payloadType;rtpHeader[2] = (rtpPacket.sequence >> 8) & 0xFF;rtpHeader[3] = rtpPacket.sequence & 0xFF;rtpHeader[4] = (rtpPacket.timestamp >> 24) & 0xFF;rtpHeader[5] = (rtpPacket.timestamp >> 16) & 0xFF;rtpHeader[6] = (rtpPacket.timestamp >> 8) & 0xFF;rtpHeader[7] = rtpPacket.timestamp & 0xFF;rtpHeader[8] = (rtpPacket.ssrc >> 24) & 0xFF;rtpHeader[9] = (rtpPacket.ssrc >> 16) & 0xFF;rtpHeader[10] = (rtpPacket.ssrc >> 8) & 0xFF;rtpHeader[11] = rtpPacket.ssrc & 0xFF;// 发送RTP包std::vector<uint8_t> packet;packet.insert(packet.end(), rtpHeader, rtpHeader + 12);packet.insert(packet.end(), payload.begin(), payload.end());sendto(socket, packet.data(), packet.size(), 0, (struct sockaddr*)&addr, sizeof(addr));}void sendRtpPacket(int socket, sockaddr_in& addr, RTPPacket& rtpPacket) {sendRtpPacket(socket, addr, rtpPacket.payload, rtpPacket);}
};// RTSP客户端
class RTSPClient {
private:int rtspSocket;std::string sessionId;int cseq;std::string serverAddress;int serverPort;int rtpPort;int rtcpPort;std::atomic<bool> isPlaying;std::thread receiveThread;std::function<void(const uint8_t*, size_t, uint32_t, bool)> frameCallback;public:RTSPClient() : rtspSocket(-1), cseq(0), serverPort(RTSP_PORT), rtpPort(0), rtcpPort(0), isPlaying(false) {}~RTSPClient() {disconnect();}bool connect(const std::string& url) {// 解析URLsize_t protocolEnd = url.find("://");if (protocolEnd == std::string::npos) {std::cerr << "Invalid RTSP URL" << std::endl;return false;}std::string protocol = url.substr(0, protocolEnd);if (protocol != "rtsp") {std::cerr << "Unsupported protocol: " << protocol << std::endl;return false;}size_t hostStart = protocolEnd + 3;size_t hostEnd = url.find(":", hostStart);size_t pathStart = url.find("/", hostStart);if (hostEnd != std::string::npos && pathStart != std::string::npos && hostEnd < pathStart) {// 包含端口号serverAddress = url.substr(hostStart, hostEnd - hostStart);serverPort = std::stoi(url.substr(hostEnd + 1, pathStart - hostEnd - 1));} else if (pathStart != std::string::npos) {// 不包含端口号,使用默认端口serverAddress = url.substr(hostStart, pathStart - hostStart);} else {std::cerr << "Invalid RTSP URL" << std::endl;return false;}std::string path = url.substr(pathStart);// 创建RTSP套接字rtspSocket = socket(AF_INET, SOCK_STREAM, 0);if (rtspSocket < 0) {std::cerr << "Failed to create RTSP socket" << std::endl;return false;}// 连接服务器sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(serverPort);inet_pton(AF_INET, serverAddress.c_str(), &serverAddr.sin_addr);if (connect(rtspSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {std::cerr << "Failed to connect to RTSP server" << std::endl;close(rtspSocket);rtspSocket = -1;return false;}// 发送OPTIONS请求std::string response = sendRequest("OPTIONS " + url + " RTSP/1.0\r\n\r\n");if (!parseResponseStatus(response, 200)) {std::cerr << "OPTIONS request failed" << std::endl;disconnect();return false;}// 发送DESCRIBE请求response = sendRequest("DESCRIBE " + url + " RTSP/1.0\r\n""Accept: application/sdp\r\n\r\n");if (!parseResponseStatus(response, 200)) {std::cerr << "DESCRIBE request failed" << std::endl;disconnect();return false;}// 解析SDP信息std::string sdp = parseSDP(response);if (sdp.empty()) {std::cerr << "Failed to parse SDP" << std::endl;disconnect();return false;}// 随机选择RTP/RTCP端口rtpPort = 10000 + (rand() % 5000) * 2;rtcpPort = rtpPort + 1;// 发送SETUP请求response = sendRequest("SETUP " + url + " RTSP/1.0\r\n""Transport: RTP/AVP;unicast;client_port=" + std::to_string(rtpPort) + "-" + std::to_string(rtcpPort) + "\r\n\r\n");if (!parseResponseStatus(response, 200)) {std::cerr << "SETUP request failed" << std::endl;disconnect();return false;}// 解析会话IDsessionId = parseSessionId(response);if (sessionId.empty()) {std::cerr << "Failed to get session ID" << std::endl;disconnect();return false;}return true;}void disconnect() {if (isPlaying) {pause();}if (rtspSocket >= 0) {// 发送TEARDOWN请求if (!sessionId.empty()) {sendRequest("TEARDOWN * RTSP/1.0\r\n""Session: " + sessionId + "\r\n\r\n");}close(rtspSocket);rtspSocket = -1;sessionId.clear();}if (receiveThread.joinable()) {receiveThread.join();}}bool play() {if (!isPlaying && rtspSocket >= 0 && !sessionId.empty()) {std::string response = sendRequest("PLAY * RTSP/1.0\r\n""Session: " + sessionId + "\r\n\r\n");if (!parseResponseStatus(response, 200)) {std::cerr << "PLAY request failed" << std::endl;return false;}isPlaying = true;receiveThread = std::thread(&RTSPClient::receiveRtpPackets, this);return true;}return false;}bool pause() {if (isPlaying && rtspSocket >= 0 && !sessionId.empty()) {isPlaying = false;if (receiveThread.joinable()) {receiveThread.join();}std::string response = sendRequest("PAUSE * RTSP/1.0\r\n""Session: " + sessionId + "\r\n\r\n");return parseResponseStatus(response, 200);}return false;}void setFrameCallback(const std::function<void(const uint8_t*, size_t, uint32_t, bool)>& callback) {frameCallback = callback;}std::string getMediaInfo() {// 这里应该返回从SDP解析的媒体信息return "Media Info: RTSP Stream";}private:std::string sendRequest(const std::string& request) {if (rtspSocket < 0) {return "";}// 增加CSeqcseq++;std::string requestWithCSeq = request;size_t crlfPos = request.find("\r\n");if (crlfPos != std::string::npos) {requestWithCSeq.insert(crlfPos + 2, "CSeq: " + std::to_string(cseq) + "\r\n");}// 发送请求send(rtspSocket, requestWithCSeq.c_str(), requestWithCSeq.size(), 0);// 接收响应char buffer[4096];memset(buffer, 0, sizeof(buffer));int bytesRead = recv(rtspSocket, buffer, sizeof(buffer) - 1, 0);return std::string(buffer, bytesRead);}bool parseResponseStatus(const std::string& response, int expectedStatus) {size_t statusStart = response.find("RTSP/1.0 ") + 9;size_t statusEnd = response.find(" ", statusStart);if (statusStart == std::string::npos || statusEnd == std::string::npos) {return false;}int statusCode = std::stoi(response.substr(statusStart, statusEnd - statusStart));return (statusCode == expectedStatus);}std::string parseSDP(const std::string& response) {size_t sdpStart = response.find("\r\n\r\n");if (sdpStart == std::string::npos) {return "";}return response.substr(sdpStart + 4);}std::string parseSessionId(const std::string& response) {size_t sessionStart = response.find("Session: ");if (sessionStart == std::string::npos) {return "";}sessionStart += 9;size_t sessionEnd = response.find("\r\n", sessionStart);if (sessionEnd == std::string::npos) {return "";}std::string sessionLine = response.substr(sessionStart, sessionEnd - sessionStart);size_t idEnd = sessionLine.find(";");if (idEnd != std::string::npos) {return sessionLine.substr(0, idEnd);}return sessionLine;}void receiveRtpPackets() {// 创建RTP套接字int rtpSocket = socket(AF_INET, SOCK_DGRAM, 0);if (rtpSocket < 0) {std::cerr << "Failed to create RTP socket" << std::endl;return;}// 绑定端口sockaddr_in localAddr;memset(&localAddr, 0, sizeof(localAddr));localAddr.sin_family = AF_INET;localAddr.sin_addr.s_addr = INADDR_ANY;localAddr.sin_port = htons(rtpPort);if (bind(rtpSocket, (struct sockaddr*)&localAddr, sizeof(localAddr)) < 0) {std::cerr << "Failed to bind RTP socket" << std::endl;close(rtpSocket);return;}// 创建缓冲区uint8_t buffer[65536];std::vector<uint8_t> nalBuffer;uint32_t currentTimestamp = 0;bool isKeyFrame = false;// 接收RTP包while (isPlaying) {sockaddr_in senderAddr;socklen_t senderAddrLen = sizeof(senderAddr);int bytesRead = recvfrom(rtpSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&senderAddr, &senderAddrLen);if (bytesRead <= 0) {continue;}// 解析RTP头部uint8_t version = (buffer[0] >> 6) & 0x03;uint8_t padding = (buffer[0] >> 5) & 0x01;uint8_t extension = (buffer[0] >> 4) & 0x01;uint8_t cc = buffer[0] & 0x0F;uint8_t marker = (buffer[1] >> 7) & 0x01;uint8_t payloadType = buffer[1] & 0x7F;uint16_t sequence = (buffer[2] << 8) | buffer[3];uint32_t timestamp = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];uint32_t ssrc = (buffer[8] << 24) | (buffer[9] << 16) | (buffer[10] << 8) | buffer[11];// 提取RTP负载size_t headerSize = 12 + (cc * 4);if (extension) {// 处理扩展头部headerSize += 4 + ((buffer[12] << 8) | buffer[13]) * 4;}const uint8_t* payload = buffer + headerSize;size_t payloadSize = bytesRead - headerSize;// 检查是否有帧回调if (frameCallback && payloadSize > 0) {// 检查NAL单元类型if (payloadSize >= 1) {uint8_t nalType = payload[0] & 0x1F;// 判断是否为关键帧isKeyFrame = (nalType == NAL_IDR_SLICE || nalType == NAL_SPS || nalType == NAL_PPS);// 处理FU-A分片if (nalType == 28 && payloadSize >= 2) {  // FU-Auint8_t fuHeader = payload[1];bool isStart = (fuHeader & 0x80) != 0;bool isEnd = (fuHeader & 0x40) != 0;if (isStart) {// 开始新的NAL单元nalBuffer.clear();// 重建原始NAL头部uint8_t originalNalHeader = (payload[0] & 0xE0) | (fuHeader & 0x1F);nalBuffer.push_back(originalNalHeader);// 更新时间戳currentTimestamp = timestamp;}// 添加分片数据nalBuffer.insert(nalBuffer.end(), payload + 2, payload + payloadSize);// 如果是结束分片且有回调,则传递完整的NAL单元if (isEnd && !nalBuffer.empty()) {frameCallback(nalBuffer.data(), nalBuffer.size(), currentTimestamp, isKeyFrame);}} else {// 非分片NAL单元,直接传递frameCallback(payload, payloadSize, timestamp, isKeyFrame);}}}}close(rtpSocket);}
};    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89821.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89821.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/89821.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络性能与应用性能的协同优化研究:基于小波变换与CNN的图像分类系统

网络性能与应用性能的协同优化研究&#xff1a;基于小波变换与CNN的图像分类系统 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 摘要 本文研究了网络性能&#xff08;延迟、带宽、丢…

【网络安全领域】CTF竞赛指南:赛事详解、热门平台与信息获取方式

CTF&#xff08;Capture The Flag&#xff09;&#xff0c;中文称为夺旗赛&#xff0c;是网络安全领域中一种备受关注和欢迎的竞赛形式。以下是关于 CTF 竞赛的详细介绍&#xff0c;以及一些参赛平台和相关咨询途径&#xff1a; CTF 竞赛详解 起源&#xff1a;CTF 起源于 199…

进程(以及系统调用和库函数概念)

计算机管理硬件&#xff1a;先去组织起来再去描述起来&#xff08;类比先去组织小组&#xff0c;再去详细描述工作&#xff09;系统调用和库函数概念&#xff1a;系统就像是银行&#xff0c;会去防着我们&#xff0c;但是会释放一些接口用于我们使用&#xff0c;这个就是叫做系…

linux + 宝塔面板 部署 django网站 启动方式:uwsgi 和gunicorn如何选择 ?

启动方式:uwsgi 和gunicorn如何选择 ? 项目uWSGIGunicorn协议uWSGI 协议&#xff08;可用 HTTP/socket&#xff09;HTTP 协议启动方式命令或 .ini 配置文件命令参数或 systemd 配置兼容框架支持 WSGI、uWSGI、FastCGI 等仅支持 WSGI性能高性能、极可调高性能、默认参数也够用配…

基于有监督学习的主动攻击检测系统

核心功能&#xff1a;登录注册功能主仪表板功能&#xff1a;实时展示检测结果和图表分析&#xff0c;模型准确率、攻击次数等。数据管理功能&#xff1a;加载训练数据、预处理数据&#xff08;使用开源KDD数据集做为模型训练数据)。模型训练功能&#xff1a;支持随机森林、支持…

simulink系列之模型接口表生成及自动连线脚本

总目录 simulink系列之汽车应用层信号处理 第一章 simulink信号处理——debounce 第二章 simulink接口表生成及自动连线脚本 目录 前言 一、simulink接口表生成脚本 1.使用方法&#xff1a; 二、模型整理连线脚本 1.使用方法&#xff1a; 总结 前言 本系列主要围绕作者采用si…

Eureka+LoadBalancer实现服务注册与发现

目录 一、相关文章 二、兼容说明 三、服务注册到EurekaServer 四、服务发现 五、LoadBalancer负载均衡 一、相关文章 基础工程&#xff1a;gradle7.6.1springboot3.2.4创建微服务工程-CSDN博客 Eureka服务端启动&#xff1a;Eureka服务端启动-CSDN博客 LoadBalancer官方…

数据存储方案h5py

对于百万级别的大规模数据&#xff08;假设 N > 1,000,000&#xff09;&#xff0c;在保证读取速度的前提下&#xff0c;需要综合考虑 存储效率、I/O 吞吐 和 内存管理。以下是针对超大规模数据的优化方案&#xff1a;&#x1f680; 终极方案&#xff1a;HDF5 (h5py) 分块存…

ARINC818协议综述

概要 航天领域ARINC818协议 协议的视频帧 协议的层次 帧格式 容器 FC协议的5个层次 8b10b编码 SOF EOF IDLEARINC818视频传输协议 ARINC818协议的容器系统 帧头控制协议FHCP 光纤通道协议 FC-AV ARINC818行场同步解析&#xff0c;上图时序图是关于行场同步小信号相关。ARINC818…

专题 二分法:查找与判定

概念解释 概述 二分法在算法竞赛中一般有这么一个用途&#xff1a;在一个具有单调性的解空间中找到符合题意的一个可行解。下面解释几个专有名词&#xff1a; 解空间 很简单&#xff0c;就是可能存在解的逻辑区域。这个在算法入门时应提到。 可行解 符合题意的解 单调性 …

硬核电子工程:从硅片到系统的全栈实战指南—— 融合电路理论、嵌入式开发与PCB设计的工程艺术

一、电路基础&#xff1a;硬件设计的底层逻辑1.1 基尔霍夫定律的硬件实现// STM32验证KVL定律&#xff08;ADC采样法&#xff09; void verify_kvl() {ADC_Enable(ADC1); // 启用ADCfloat Vr1 read_ADC(PA0) * 3.3 / 4096; // 读取R1电压float Vr2 read_ADC(PA1) * 3.3 / 4…

Linux网络:序列化与反序列化

引入&#xff1a;面向字节流 TCP是面向字节流的&#xff0c;如果按照字节流来读取信息&#xff0c;可能会出问题 比如客户传入“1100”&#xff0c;服务器读入“11”&#xff0c;后面的00被当作下一条信息&#xff0c;这就出问题了 我们可以将多个信息合并为一个字符串 在发送信…

二、Spark 开发环境搭建 IDEA + Maven 及 WordCount 案例实战

作者&#xff1a;IvanCodes 日期&#xff1a;2025年7月20日 专栏&#xff1a;Spark教程 本教程将从零开始&#xff0c;一步步指导您如何在 IntelliJ IDEA 中搭建一个基于 Maven 和 Scala 的 Spark 开发环境&#xff0c;并最终完成经典的 WordCount 案例。 一、创建 Maven 项目…

【python】算法实现1

实现一个动态规划算法 def dynamic_programming_example(n: int) -> List[int]:"""动态规划示例&#xff1a;计算斐波那契数列参数:- n: 斐波那契数列的项数返回:- List[int]: 斐波那契数列前n项"""if n < 0:return []elif n 1:return […

C++控制台贪吃蛇开发:从0到1绘制游戏世界

资料合集下载链接: ​​https://pan.quark.cn/s/472bbdfcd014​ 本文将带你一步步实现以下目标: 初始化游戏元素(边界、蛇、食物)的数据。 绘制静态的游戏边界(墙)。 在指定位置显示蛇和食物。 学习并使用Windows API来精确定位光标,实现“指哪打哪”的绘图。 隐藏闪烁…

共享模式、社群与开源链动2+1模式AI智能名片S2B2C商城小程序的协同发展研究

摘要&#xff1a;本文深入探讨了共享模式与社群之间的内在联系&#xff0c;指出信用体系完善是共享模式前提&#xff0c;信任源于相同认知促使共享在社群中更易发生。同时&#xff0c;引入开源链动21模式AI智能名片S2B2C商城小程序这一新兴元素&#xff0c;分析其在共享模式与社…

LeetCode 322. 零钱兑换 LeetCode 279.完全平方数 LeetCode 139.单词拆分 多重背包基础 56. 携带矿石资源

LeetCode 322. 零钱兑换 思路1&#xff1a; 回溯算法可以做&#xff0c;只要存储数组的最小长度即可&#xff0c;但可能会超时。思路2: 相当于是求最大价值的相反面&#xff0c;另外一个物品可以使用多次&#xff0c;因此是个完全背包。因此这是个完全背包的求最小价值类型题…

JAVA面试宝典 -《Elasticsearch 深度调优实战》

文章目录一、引言&#xff1a;搜索引擎为啥越来越慢&#xff1f;1.1 典型业务场景性能瓶颈表现​​&#xff1a;二、倒排索引压缩&#xff1a;让存储与检索更高效&#x1f9e0; 2.1倒排索引结构简述&#x1f527; 2.2 压缩算法三剑客✅ 调优建议三、分片策略&#xff1a;写入性…

克鲁斯焊接机器人保护气省气方案

在现代焊接工艺中&#xff0c;克鲁斯焊接机器人扮演着至关重要的角色。随着制造业对成本控制和可持续发展的日益重视&#xff0c;焊接过程中的保护气省气问题成为了焦点。WGFACS节气装置为克鲁斯焊接机器人的保护气省气提供了一种创新且有效的解决方案。克鲁斯焊接机器人以其高…

JavaEE——多线程中的哈希表

目录前言1.HashTable2.ConcurrentHashMap总结前言 在使用多线程前&#xff0c;我们用HashMap类来创建哈希表&#xff0c;但这个类线程不安全&#xff0c;在这篇文章&#xff0c;我们将介绍多线程环境的哈希表&#xff0c;将会讲述HashTable, HashMap, ConcurrentHashMap这三个…