C++原生socket检测RTSP流是否可用

方式一

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值