使用live555制作rtsp客户端,捕获h264等解码

Posted qianbo_insist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用live555制作rtsp客户端,捕获h264等解码相关的知识,希望对你有一定的参考价值。

1、live555

live555是一个优秀的rtsp lib,坚持了很多年,细看代码,是精炼的,值得研读。

2、rtsp

rtsp协议包含了rtp,rtcp,sdp等协议,需要把这几个协议都有一定的了解,才能更好地理解代码。

3、show me the code

以下是客户端代码,不作过多解释,值得注意的地方:
1 需要设定接收缓冲区大小,对于高分辨率的视频来说,系统socket缓冲区需要设大一些
2 需要知道服务端支持tcp方式还是udp方式
3 断线需要重连
4 实际上,live555帮我们做了保活连接

/* ---------------------------------------------------------------------------
**
2021-02-13
email 418511899@qq.com
** 
** -------------------------------------------------------------------------*/

#pragma once

#include "BasicUsageEnvironment.hh"
#include "liveMedia.hh"
#include <string>
#include <iostream>
#ifdef WIN32
#pragma warning (disable: 4512) 
#pragma warning (disable: 4100) 
#pragma warning (disable: 4091) 
#include <stdint.h>
typedef int ssize_t;
#endif



#define RTSP_CALLBACK(uri, resultCode, resultString) \\
static void continueAfter ## uri(RTSPClient* rtspClient, int resultCode, char* resultString)  static_cast<c_rtsp::RTSPClientConnection*>(rtspClient)->continueAfter ## uri(resultCode, resultString);  \\
void continueAfter ## uri (int resultCode, char* resultString); \\
/**/

#define TASK_CALLBACK(class,task) \\
TaskToken m_ ## task ## Task; \\
static void Task ## task(void* rtspClient)  static_cast<class*>(rtspClient)->Task ## task();  \\
void Task ## task (); \\
/**/


#if LIVEMEDIA_LIBRARY_VERSION_INT > 1371168000 
	#define RTSPClientConstrutor(env, url, verbosity, appname, httpTunnelPort) RTSPClient(env, url, verbosity, appname, httpTunnelPort ,-1)
#else					
	#define RTSPClientConstrutor(env, url, verbosity, appname, httpTunnelPort) RTSPClient(env, url, verbosity, appname, httpTunnelPort)
#endif



class Environment : public BasicUsageEnvironment

public:
	Environment(char * stop) : BasicUsageEnvironment(*BasicTaskScheduler::createNew()), m_stop(stop)
	
		m_stop = stop;
	

	~Environment()
	
		TaskScheduler* scheduler = &this->taskScheduler();
		delete scheduler;
	

	void mainloop()
	
		this->taskScheduler().doEventLoop(m_stop);
	

	void stop()
	
		*m_stop = 1;
	


protected:
	char* m_stop;
;


typedef void (*callback_onData)(void * puser, uint8_t*, ssize_t len);


class c_rtsp

	public:
		class Callback
		
			public:
				virtual bool    onNewSession(const char* id, const char* media, 
					const char* codec, const char* sdp)  return true; 
				virtual bool    onData(const char* id, unsigned char* buffer,
					ssize_t size, struct timeval presentationTime)
				
					if (v_callback != NULL) 
						//回调函数,传回名称和数据,数据大小+头部的大小
						v_callback(v_user, buffer, size);
						return true;
					
					return false;
				
				virtual ssize_t onNewBuffer(unsigned char* buffer, ssize_t size) 
				
					return 0; 
				
				virtual void    onError(c_rtsp&, const char*message)  
					std::cout << v_name << ":Error:" << message << std::endl;
				
				virtual void    onConnectionTimeout(c_rtsp& connection)
				
					std::cout << v_name << ":Connection timeout -> retry" << std::endl;
					v_callback(v_user, NULL, -1);
					connection.start();
				
				virtual void    onDataTimeout(c_rtsp& connection)
				
					std::cout << v_name << ":Data timeout -> retry" << std::endl;
					v_callback(v_user, NULL, -1);
					connection.start();
				
				//增加的头部长度
				int v_header = 0;
				std::string v_name= "empty" ;
				void RegisterCallBack(void * puser, int headlen, callback_onData cb) 
					v_user = puser;
					v_header = headlen;
					v_callback = cb;
				
				callback_onData v_callback = NULL;
				void * v_user = NULL;
		;

	protected:

		class SessionSink: public MediaSink 
		
			public:
				static SessionSink* createNew(UsageEnvironment& env, Callback* callback)  return new SessionSink(env, callback); 

			private:
				SessionSink(UsageEnvironment& env, Callback* callback);
				virtual ~SessionSink();

				void allocate(ssize_t bufferSize);

				static void afterGettingFrame(void* clientData, unsigned frameSize,
							unsigned numTruncatedBytes,
							struct timeval presentationTime,
							unsigned durationInMicroseconds)
				
					static_cast<SessionSink*>(clientData)->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
				
				
				void afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds);

				virtual Boolean continuePlaying();

			private:
				u_int8_t*              m_buffer;
				ssize_t                 m_bufferSize;
				Callback*              m_callback; 	
		;
	
	
		class RTSPClientConnection : public RTSPClient
		
			public:
				RTSPClientConnection(c_rtsp& connection, Environment& env, Callback* callback, const char* rtspURL, int timeout, bool rtpovertcp, int verbosityLevel);
				virtual ~RTSPClientConnection(); 
			
			protected:
				void sendNextCommand(); 
						
				RTSP_CALLBACK(DESCRIBE,resultCode,resultString);
				RTSP_CALLBACK(SETUP,resultCode,resultString);
				RTSP_CALLBACK(PLAY,resultCode,resultString);
			
				TASK_CALLBACK(c_rtsp::RTSPClientConnection,ConnectionTimeout);
				TASK_CALLBACK(c_rtsp::RTSPClientConnection,DataArrivalTimeout);
				
			protected:
				c_rtsp&          m_connection;
				int                      m_timeout;
				bool                     m_rtpovertcp;
				MediaSession*            m_session;                   
				MediaSubsession*         m_subSession;             
				MediaSubsessionIterator* m_subSessionIter;
				Callback               * m_callback; 	
				unsigned int             m_nbPacket;
		;
		
	public:
		c_rtsp(const char* rtspURL,
			int header = 4,
			int timeout = 5, 
			bool rtpovertcp = false, 
			int verbosityLevel = 0);
		virtual ~c_rtsp();

		void Register_Callcack(void * puser,const char * name, int headlen, callback_onData cb)
		
			if (name != NULL)
				m_callback.v_name = name;
			else
				m_callback.v_name = "empty";
			m_callback.RegisterCallBack(puser, headlen, cb);
		
		void start(unsigned int delay = 0);
		void stop();

	protected:
		TASK_CALLBACK(c_rtsp,startCallback);
	
	protected:
		Environment              m_env;
		Callback                 m_callback; 	
		std::string              m_url;
		int                      m_timeout = 2;
		bool                     m_rtpovertcp = false;
		int                      m_verbosity = 0;
	
		RTSPClientConnection*    m_rtspClient;
;

以上为头文件定义,其中比较Register_Callcack ,注册回调函数,将来在自己的文件里面处理收到的包。下面给出cpp



#include "common.h"
#include "c_rtsp.h"


c_rtsp::SessionSink::SessionSink(UsageEnvironment& env, Callback* callback)
	: MediaSink(env)
	, m_buffer(NULL)
	, m_bufferSize(0)
	, m_callback(callback) 
	//, m_markerSize(0)

	allocate(512*1024);


c_rtsp::SessionSink::~SessionSink()

	delete [] m_buffer;


void c_rtsp::SessionSink::allocate(ssize_t bufferSize)

	m_bufferSize = bufferSize;
	m_buffer = new u_int8_t[m_bufferSize];
	if (m_callback)
	
		m_callback->onNewBuffer(m_buffer, m_bufferSize);
	



void c_rtsp::SessionSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime, unsigned durationInMicroseconds)

	if (numTruncatedBytes != 0)
	
		delete [] m_buffer;
		envir() << "buffer too small " << (int)m_bufferSize << " allocate bigger one\\n";
		allocate(m_bufferSize*2);
	
	else if (m_callback)
	

		if (!m_callback->onData(this->name(), m_buffer, frameSize+m_callback->v_header, presentationTime))
		
			envir() << "NOTIFY failed\\n";
		
	
	this->continuePlaying();


Boolean c_rtsp::SessionSink::continuePlaying()

	Boolean ret = False;
	if (source() != NULL)
	
		int header = m_callback->v_header;
		source()->getNextFrame(m_buffer+header, m_bufferSize-header,
				afterGettingFrame, this,
				onSourceClosure, this);
		ret = True;
	
	return ret;	



c_rtsp::c_rtsp(const char* rtspURL, int header,
	int timeout, bool rtpovertcp, int verbosityLevel)
				: m_startCallbackTask(NULL)
				, m_env(0)
				, m_url(rtspURL)
				, m_timeout(timeout)
				, m_rtpovertcp(rtpovertcp)
				, m_verbosity(verbosityLevel)
				, m_rtspClient(NULL)

	


void c_rtsp::start(unsigned int delay)

	m_startCallbackTask = m_env.taskScheduler().scheduleDelayedTask(delay*1000000, TaskstartCallback, this);
	//start();
	m_env.mainloop();
	
void c_rtsp::stop()

	m_env.stop();

void c_rtsp::TaskstartCallback()

	if (m_rtspClient)
	
		
		Medium::close(m_rtspClient);
	
	
	m_rtspClient = new RTSPClientConnection(*this, m_env, &m_callback, m_url.c_str(), m_timeout, m_rtpovertcp, m_verbosity);	


c_rtsp::~c_rtsp()

	m_env.taskScheduler().unscheduleDelayedTask(m_startCallbackTask);
	Medium::close(m_rtspClient);


		
c_rtsp::RTSPClientConnection::RTSPClientConnection(c_rtsp& connection, Environment& env, Callback* callback, const char* rtspURL, int timeout, bool rtpovertcp, int verbosityLevel)
				: RTSPClientConstrutor(env, rtspURL, verbosityLevel, NULL, 0)
				, m_connection(connection)
				, m_timeout(timeout)
				, m_rtpovertcp(rtpovertcp)
				, m_session(NULL)
				, m_subSessionIter(NULL)
				, m_callback(callback)
				, m_nbPacket(0)

	// start tasks
	m_ConnectionTimeoutTask = envir().taskScheduler().scheduleDelayedTask(m_timeout*1000000, TaskConnectionTimeout, this);
	
	// initiate connection process
	this->sendNextCommand();


c_rtsp::RTSPClientConnection::~RTSPClientConnection()

	envir().taskScheduler().unscheduleDelayedTask(m_ConnectionTimeoutTask);
	envir().taskScheduler().unscheduleDelayedTask(m_DataArrivalTimeoutTask);
	
	delete m_subSessionIter;
	
	// free subsession
	if (m_session != NULL) 
	
		MediaSubsessionIterator iter(*m_session);
		MediaSubsession* subsession;
		while ((subsession = iter.next()) != NULL) 
		
			if (subsession->sink) 
			
				envir() << "Close session: " << subsession->mediumName() << "/" << subsession->codecName() << "\\n";
				Medium::close(subsession->sink);
				subsession->sink = NULL;
			
			
		Medium::close(m_session);
	

static unsigned getBufferSize(UsageEnvironment& env, int bufOptName,
	int socket) 
	unsigned curSize;
	socklen_t sizeSize = sizeof curSize;
	if (getsockopt(socket, SOL_SOCKET, bufOptName,
		(char*)&curSize, &sizeSize) < 0) 
		env<<"getBufferSize() error: ";
		return 0;
	

	return curSize;

static unsigned setBufferTo(UsageEnvironment& env, int bufOptName,
	int socket, unsigned requestedSize) 
	socklen_t sizeSize = sizeof requestedSize;
	setsockopt(socket, SOL_SOCKET, bufOptName, (char*)&requestedSize, sizeSize);

	// Get and return the actual, resulting buffer size:
	return getBufferSize(env, bufOptName, socket);


void c_rtsp::RTSPClientConnection::sendNextCommand()

	if (m_subSessionIter == NULL)
	
		// no SDP, send DESCRIBE
		this->sendDescribeCommand(continueAfterDESCRIBE); 
	
	else
	
		m_subSession = m_subSessionIter->next();
		if (m_subSession != NULL) 
		
			// still subsession to SETUP
			if (!m_subSession->initiate()) 
			
				envir() << "Failed to initiate " << m_subSession->mediumName() << "/" << m_subSession->codecName() << " subsession: " << envir().getResultMsg() << "\\n";
				this->sendNextCommand();
			 
			else 
			
#define RECV_BUFFER 1024*1024
				Environment &env = m_connection.m_env;
				int socketNum = m_subSession->rtpSource()->RTPgs()->socketNum();
				unsigned curBufferSize = setBufferTo(env, SO_RCVBUF, socketNum, 1024 * 1024);
				if (curBufferSize < RECV_BUFFER)
				
					env << "socket Buff is small than set,check system set! \\r\\n";
				
				if (fVerbosityLevel > 1) 
								
					envir() << "Initiated " << m_subSession->mediumName() << "/" << m_subSession以上是关于使用live555制作rtsp客户端,捕获h264等解码的主要内容,如果未能解决你的问题,请参考以下文章

live555移植到hi3516做rtsp服务器

使用 Live555 从连接到 H264 编码器的 IP 摄像机流式传输实时视频

调用Live555接收RTSP直播流,转换为Http Live Streaming(iOS直播)协议

修改live555支持mpeg2ts RTSP拉流,附代码

vlc源码分析之调用live555接收RTSP数据

在 ffplay 中获得绿屏:使用 Live555 通过 RTP 流将桌面(DirectX 表面)流式传输为 H264 视频