方式一:
rtsp_video_status.h
#ifndef __INCLUDE_RTSP_CLIENT_H__
#define __INCLUDE_RTSP_CLIENT_H__
#include <thread>
#include <mutex>
#include <ctime>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
typedef enum{
RTSP_OPTIONS = 0,
RTSP_DESCRIBE = 1,
RTSP_SETUP = 2,
RTSP_PLAY = 3,
RTSP_PAUSE = 4,
RTSP_TEARDOWN = 5,
RTSP_SET_PARAMETER = 6,
RTSP_GET_PARAMETER = 7,
RTSP_METHOD_MAX
}RtspMethodT;
struct RtspMethodStr{
int method;
const char* method_str;
};
const RtspMethodStr g_method[RTSP_METHOD_MAX] = {
{RTSP_OPTIONS, "OPTIONS"},
{RTSP_DESCRIBE, "DESCRIBE"},
{RTSP_SETUP, "SETUP"},
{RTSP_PLAY, "PLAY"},
{RTSP_PAUSE, "PAUSE"},
{RTSP_TEARDOWN, "TEARDOWN"},
{RTSP_SET_PARAMETER, "SET_PARAMETER"},
{RTSP_GET_PARAMETER, "GET_PARAMETER"},
};
struct RtpTcpHdr
{
int8_t dollar;
int8_t channel;
int16_t len;
};
class RtspVideoStatus
{
public:
RtspVideoStatus(const std::string& ip, int port, const std::string& rtsp);
~RtspVideoStatus();
void init();
bool is_alive() { return m_is_alive; }
private:
void open_sock();
void close_sock();
int recv_data();
int handle_data();
int parse_data( const char* data, int len );
int handle_cmd( const char* data, int len );
void watch_alive_thread();
private:
std::string m_ip;
int m_port;
int m_sock;
struct sockaddr_in m_server_addr;
enum{
MAX_RECV_BUF_LEN = 1024*4,
};
char m_recv_buf[MAX_RECV_BUF_LEN];
uint32_t m_recv_len;
std::mutex m_mutex;
private:
int get_str( const char* data, const char* s_mark, bool with_s_make, const char* e_mark, bool with_e_make, char* dest );
int parse_rsp_code( const char* data, int len );
int send_simple_cmd( RtspMethodT method );
int send_describe_cmd();
int send_setup_cmd();
// int send_play_cmd( int s_sec, int e_sec );
int parser_describe( const char* data, int len );
int parser_setup( const char* data, int len );
int send_cmd( const char* data, int len );
private:
int m_cseq;
int m_rtp_ch;
char m_session[128];
char m_base_url[256];
RtspMethodT m_method;
enum{
MAX_MEDIA_NUM = 2,
};
struct MediaInfo{
int index;
char track_id[128];
};
MediaInfo m_media_info[MAX_MEDIA_NUM];
int m_media_num;
int m_media_index;
bool m_is_alive;
bool m_terminate;
};
#endif
rtsp_video_status.cpp
#include "rtsp_video_status.h"
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
RtspVideoStatus::RtspVideoStatus(const std::string& ip, int port, const std::string& rtsp)
: m_ip(ip)
, m_port(port)
, m_terminate(false)
{
memset(m_base_url, 0, sizeof(m_base_url));
strcpy(m_base_url, rtsp.c_str());
std::thread(&RtspVideoStatus::watch_alive_thread, this).detach();
}
RtspVideoStatus::~RtspVideoStatus()
{
m_terminate = true;
close(m_sock);
}
void RtspVideoStatus::init()
{
memset( m_recv_buf, 0, sizeof(m_recv_buf) );
m_recv_len = 0;
m_cseq = 1;
m_rtp_ch = 0;
memset( m_session, 0, sizeof(m_session) );
m_method = RTSP_METHOD_MAX;
memset( &m_media_info, 0, sizeof(m_media_info) );
m_media_num = 0;
m_media_index = 0;
m_is_alive = false;
}
void RtspVideoStatus::open_sock()
{
if ((m_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
return ;
}
memset(&m_server_addr, 0, sizeof(m_server_addr));
m_server_addr.sin_family = AF_INET;
m_server_addr.sin_port = htons(m_port);
if (inet_pton(AF_INET, m_ip.c_str(), &m_server_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
close(m_sock);
return;
}
if (connect(m_sock, (struct sockaddr*)&m_server_addr, sizeof(m_server_addr)) == -1) {
close(m_sock);
return;
}
}
void RtspVideoStatus::close_sock()
{
if (m_sock != -1) {
close(m_sock);
m_sock = -1;
}
}
int RtspVideoStatus::recv_data()
{
int recv_len = sizeof(m_recv_buf)-1-m_recv_len;
if( recv_len <= 0 ){
printf( "recv buf len <=0\n" );
return -1;
}
int ret = recv(m_sock, m_recv_buf+m_recv_len, recv_len, 0 );
if( ret < 0 ){
printf( "recv data failed\n" );
return -1;
}
m_recv_len += ret;
return ret;
}
int RtspVideoStatus::handle_data()
{
char *recv_buf = m_recv_buf;
m_recv_buf[m_recv_len] = '\0';
int ret = -1;
while( m_recv_len > 0 ){
ret = 0;
if( '$' == *recv_buf ){
if( m_recv_len <= sizeof(struct RtpTcpHdr) )
break;
struct RtpTcpHdr* r_t_hd = (struct RtpTcpHdr *)recv_buf;
uint32_t r_t_len = ntohs(r_t_hd->len);
if( m_recv_len < r_t_len + 4 )
break;
//handle rtcp
m_recv_len -= r_t_len + 4;
recv_buf += r_t_len + 4;
}else{
int parser_ret = parse_data( recv_buf, m_recv_len );
if( parser_ret < 0 )
break;
if( handle_cmd( recv_buf, parser_ret ) < 0 )
return -1;
m_recv_len -= parser_ret;
recv_buf += parser_ret;
}
}
if( m_recv_len > 0 )
memmove( m_recv_buf , recv_buf, m_recv_len );
return ret;
}
int RtspVideoStatus::parse_data( const char* data, int len )
{
const char *start = data;
const char *end_mark = "\r\n\r\n";
const char *end = NULL;
if( NULL == (end = strstr(start, end_mark)) )
return -1;
int header_len = end - start + strlen(end_mark);
int content_len = 0;
const char* conten_len_mark = "Content-Length: ";
const char* content_len_pos = strstr(data, conten_len_mark);
if( content_len_pos != NULL && strstr(content_len_pos, "\r\n") != NULL )
content_len = atoi( content_len_pos+strlen(conten_len_mark) );
if( len < (header_len + content_len) )
return -1;
return (header_len + content_len);
}
int RtspVideoStatus::handle_cmd( const char* data, int len )
{
int code = parse_rsp_code( data, len );
if( code != 200 ){
printf( "response code id not 200 ok, code:%d\n", code);
return -1;
}
int ret = 0;
switch( m_method ){
case RTSP_OPTIONS:
ret = send_describe_cmd();
m_method = RTSP_DESCRIBE;
break;
case RTSP_DESCRIBE:
parser_describe( data, len );
ret = send_setup_cmd();
m_method = RTSP_SETUP;
break;
case RTSP_SETUP:
parser_setup( data, len );
if( m_media_index < m_media_num )
ret = send_setup_cmd();
else{
// ret = send_play_cmd( 0, -1 );
m_method = RTSP_METHOD_MAX;
//setup ok
m_is_alive = true;
}
break;
default:
break;
}
return ret;
}
int RtspVideoStatus::get_str( const char* data, const char* s_mark, bool with_s_make, const char* e_mark, bool with_e_make, char* dest )
{
const char* satrt = strstr( data, s_mark );
if( satrt != NULL ){
const char* end = strstr( satrt, e_mark );
if( end != NULL ){
int s_pos = with_s_make ? 0 : strlen(s_mark);
int e_pos = with_e_make ? strlen(e_mark) : 0;
strncpy( dest, satrt+s_pos, (end-e_pos) - (satrt+s_pos) );
}
return 0;
}
return -1;
}
int RtspVideoStatus::parse_rsp_code( const char* data, int len )
{
const char* rtsp_pos = strstr( data, "RTSP/" );
if( rtsp_pos != data ){
printf( "response format error\n" );
return -1;
}
const char* pos = strstr( rtsp_pos+strlen("RTSP/"), " " );
if( pos == NULL ){
printf( "response format error\n" );
return -1;
}
int code = atoi( pos+1 );
if( code < 100 || code > 999 ){
printf( "response format error\n" );
return -1;
}
return code;
}
int RtspVideoStatus::send_simple_cmd( RtspMethodT method )
{
char cmd[512] = "";
snprintf( cmd, sizeof(cmd), "%s %s RTSP/1.0\r\nCSeq: %d\r\n%s\r\n", g_method[method].method_str, m_base_url, m_cseq++, m_session );
return send_cmd( cmd, strlen(cmd) );
}
int RtspVideoStatus::send_describe_cmd()
{
char cmd[512] = "";
snprintf( cmd, sizeof(cmd), "DESCRIBE %s RTSP/1.0\r\nCSeq: %d\r\n%sAccept: application/sdp\r\n\r\n", m_base_url, m_cseq++, m_session );
return send_cmd( cmd, strlen(cmd) );
}
int RtspVideoStatus::send_setup_cmd()
{
char cmd[512] = "";
snprintf( cmd, sizeof(cmd), "SETUP %s%s%s RTSP/1.0\r\nCSeq: %d\r\n%sTransport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n\r\n",
m_base_url, m_base_url[strlen(m_base_url)-1]=='/'?"":"/", m_media_info[m_media_index++].track_id, m_cseq++, m_session, m_rtp_ch, m_rtp_ch+1 );
return send_cmd( cmd, strlen(cmd) );
}
// int RtspVideoStatus::send_play_cmd( int s_sec, int e_sec )
// {
// char range[64] = "";
// if( s_sec != -1 ){
// if( e_sec != -1 )
// snprintf( range, sizeof(range), "Range: npt=%d.00-%d.00\r\n", s_sec, e_sec );
// else
// snprintf( range, sizeof(range), "Range: npt=%d.00-\r\n", s_sec );
// }
// char cmd[512] = "";
// snprintf( cmd, sizeof(cmd), "PLAY %s RTSP/1.0\r\nCSeq: %d\r\n%s%s\r\n", m_base_url, m_cseq++, m_session, range );
// return send_cmd( cmd, strlen(cmd) );
// }
int RtspVideoStatus::parser_describe( const char* data, int len )
{
get_str( data, "Content-Base: ", false, "\r\n", false, m_base_url );
const char* ptr = data;
for( int i = 0; i < MAX_MEDIA_NUM; i++ ){
const char* media_pos = strstr( ptr, "m=" );
if( media_pos != NULL ){
ptr = media_pos+2;
m_media_num++;
m_media_info[i].index = i;
get_str( media_pos, "a=control:", false, "\r\n", false, m_media_info[i].track_id );
}else
break;
}
return 0;
}
int RtspVideoStatus::parser_setup( const char* data, int len )
{
get_str( data, "Session:", true , "\r\n", true, m_session );
const char* pos = strstr( m_session, ";" );
if( pos != NULL ){
char session[128] = "";
strncpy( session, m_session, pos-m_session );
memset( m_session, 0, sizeof(m_session) );
snprintf( m_session, sizeof(m_session), "%s\r\n", session );
}
return 0;
}
int RtspVideoStatus::send_cmd( const char* data, int len )
{
return send(m_sock, data, len, 0 );
}
void RtspVideoStatus::watch_alive_thread()
{
while(!m_terminate)
{
init();
open_sock();
// OPTIONS, DESCRIBE, SETUP trackId=1, SETUP trackId=2, PLAY, TEARDOWN
m_method = RTSP_OPTIONS;
send_simple_cmd(RTSP_OPTIONS);
for (int i = 0;i < 4;i++)
{
recv_data();
handle_data();
}
m_method = RTSP_TEARDOWN;
send_simple_cmd(RTSP_TEARDOWN);
close_sock();
sleep(30);
}
}
方式二:
rtsp_client.h
// rtsp_client.h
#ifndef RTSP_CLIENT_H
#define RTSP_CLIENT_H
#include <string>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
class RtspClient {
public:
// 构造函数
RtspClient(const std::string& uri);
// 启动客户端并执行完整流程
bool run();
private:
std::string m_ip;
std::string m_uri;
int m_port;
int m_cseq;
std::string m_session_id;
// socket
SOCKET m_socket;
// 初始化 Winsock
bool init_winsock();
// 创建连接
bool connect_to_server();
// 发送 RTSP 请求
void send_request(const std::string& method, const std::string& headers = "", const std::string& uri = "");
// 接收响应
int receive_response();
// 解析 Session ID
void parse_session_id(const std::string& response);
// 执行各阶段请求
bool send_options();
bool send_describe();
bool send_setup();
bool send_play();
bool send_teardown();
void parse_rtsp_url(const std::string& url, std::string& ipAddress, int& port);
// 清理资源
void cleanup();
};
#endif // RTSP_CLIENT_H
rtsp_client.cpp
// rtsp_client.cpp
#include "rtsp_client.h"
#include <stdio.h>
#include <string.h>
RtspClient::RtspClient(const std::string& uri)
: m_uri(uri), m_cseq(1) {
m_socket = INVALID_SOCKET;
parse_rtsp_url(uri, m_ip, m_port);
}
bool RtspClient::run() {
if (!init_winsock()) return false;
if (!connect_to_server()) return false;
if (!send_options()) return false;
if (!send_describe()) return false;
if (!send_setup()) return false;
if (!send_play()) return false;
if (!send_teardown()) return false;
cleanup();
return true;
}
bool RtspClient::init_winsock() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup failed with error: %d\n", WSAGetLastError());
return false;
}
return true;
}
bool RtspClient::connect_to_server() {
struct sockaddr_in serverAddr;
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socket == INVALID_SOCKET) {
printf("Socket creation failed with error: %ld\n", WSAGetLastError());
return false;
}
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(m_port);
inet_pton(AF_INET, m_ip.c_str(), &serverAddr.sin_addr);
if (connect(m_socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
printf("Connect failed with error: %d\n", WSAGetLastError());
return false;
}
printf("Connected to RTSP server at %s:%d\n", m_ip.c_str(), m_port);
return true;
}
void RtspClient::send_request(const std::string& method, const std::string& headers, const std::string& uri) {
std::string cur_uri = m_uri;
if (!uri.empty())
cur_uri = uri;
char request[2048];
snprintf(request, sizeof(request),
"%s %s RTSP/1.0\r\n"
"CSeq: %d\r\n"
"User-Agent: MyRTSPClient/1.0\r\n"
"%s"
"\r\n",
method.c_str(), cur_uri.c_str(), m_cseq++, headers.c_str());
printf("Sending Request:\n%s\n", request);
send(m_socket, request, strlen(request), 0);
}
int RtspClient::receive_response() {
char buffer[4096];
int bytes_received = recv(m_socket, buffer, sizeof(buffer) - 1, 0);
if (bytes_received <= 0) {
printf("Failed to receive response\n");
return -1;
}
buffer[bytes_received] = '\0';
printf("Received Response:\n%s\n", buffer);
parse_session_id(buffer);
if (strstr(buffer, "200 OK")) {
return 0; // 成功
}
else {
printf("Request failed\n");
return -1;
}
}
void RtspClient::parse_session_id(const std::string& response) {
size_t start_pos = response.find("Session:");
if (start_pos != std::string::npos) {
std::string substring = response.substr(start_pos + sizeof ("Session:"));
size_t end_pos = substring.find(";");
if (end_pos != std::string::npos) {
std::string str_session = substring.substr(0, end_pos);
m_session_id = str_session;
}
}
}
bool RtspClient::send_options() {
send_request("OPTIONS", "");
return receive_response() == 0;
}
bool RtspClient::send_describe() {
std::string headers = "Accept: application/sdp\r\n";
send_request("DESCRIBE", headers);
return receive_response() == 0;
}
bool RtspClient::send_setup() {
std::string headers = "Transport: RTP/AVP;unicast;client_port=61706-61707\r\n";
std::string uri = m_uri + "/trackID=0";
send_request("SETUP", headers, uri);
if (receive_response())
return false;
uri = m_uri + "/trackID=1";
send_request("SETUP", headers, uri);
return receive_response() == 0;
}
bool RtspClient::send_play() {
std::string headers;
headers = "Session: " + m_session_id + "\r\n";
headers += "Range: npt=0.000-\r\n"; // 可选,指定播放范围
send_request("PLAY", headers);
return receive_response() == 0;
}
bool RtspClient::send_teardown() {
std::string headers;
if (!m_session_id.empty()) {
headers = "Session: " + m_session_id + "\r\n";
}
send_request("TEARDOWN", headers);
return receive_response() == 0;
}
void RtspClient::cleanup() {
if (m_socket != INVALID_SOCKET) {
closesocket(m_socket);
}
WSACleanup();
}
void RtspClient::parse_rtsp_url(const std::string& url, std::string& ipAddress, int& port) {
// 假定URL格式为 rtsp://[username:password@]ipaddress[:port][/path]
std::string tempUrl = url;
// 移除 "rtsp://"
size_t protocolEnd = tempUrl.find("://");
if (protocolEnd == std::string::npos) {
printf("rtsp_video_status Invalid RTSP URL format:%s\n", url.c_str());
return;
}
tempUrl = tempUrl.substr(protocolEnd + 3);
// 查找 '@' 以跳过可能的用户名密码部分
size_t atIndex = tempUrl.find('@');
if (atIndex != std::string::npos) {
tempUrl = tempUrl.substr(atIndex + 1);
}
// 找到第一个斜杠,其前的部分包含 IP 地址和端口
size_t slashIndex = tempUrl.find('/');
std::string hostPart = (slashIndex != std::string::npos) ? tempUrl.substr(0, slashIndex) : tempUrl;
// 根据冒号分割 IP 地址和端口
size_t colonIndex = hostPart.find(':');
if (colonIndex != std::string::npos) {
ipAddress = hostPart.substr(0, colonIndex);
port = atoi(hostPart.substr(colonIndex + 1).c_str());
}
else {
// 如果没有找到冒号,默认端口是554(RTSP默认端口)
ipAddress = hostPart;
port = 554;
}
}
main.cpp
// main.cpp
#include "rtsp_client.h"
#include <iostream>
int main() {
std::string uri = "rtsp://192.168.5.67:8554/test"; // 替换为你的流路径
RtspClient client(uri);
if (client.run()) {
std::cout << "RTSP 流检测成功完成。\n";
}
else {
std::cerr << "RTSP 流检测失败。\n";
}
return 0;
}
输出如下:
wireshark抓包如下:
搭建测试环境:
1、下载rtsp-simple-server,运行exe启动
https://download.youkuaiyun.com/download/qq_23350817/88495245
2、使用ffmpeg推流:
ffmpeg -re -stream_loop -1 -i 1.mp4 -c copy -f rtsp rtsp://192.168.5.67:8554/test
IP根据自己电脑IP进行修改(IP最好不要配置成127.0.0.1,我测试的时候失效了)。
3、打开wireshark,抓取loopback网卡的包,设置"rtsp"进行过滤,运行当前程序test。