分析一个通用的rtsp server实现过程串联模块
来源:互联网 发布:自学武术软件 编辑:程序博客网 时间:2024/06/05 08:50
简单的RTSP消息交互过程
C表示RTSP客户端,S表示RTSP服务端
第一步:查询服务器端可用方法
C->S OPTION request //询问S有哪些方法可用
S->C OPTION response //S回应信息的public头字段中包括提供的所有可用方法
第二步:得到媒体描述信息
C->S DESCRIBE request //要求得到S提供的媒体描述信息
S->C DESCRIBE response //S回应媒体描述信息,一般是sdp信息
第三步:建立RTSP会话
C->S SETUP request //通过Transport头字段列出可接受的传输选项,请求S建立会话
S->C SETUP response //S建立会话,通过Transport头字段返回选择的具体转输选项,并返回建立的Session ID;
第四步:请求开始传送数据
C->S PLAY request //C请求S开始发送数据
S->C PLAY response //S回应该请求的信息
第五步: 数据传送播放中
S->C 发送流媒体数据 // 通过RTP协议传送数据
第六步:关闭会话,退出
C->S EARDOWN request //C请求关闭会话
S->C TEARDOWN response //S回应该请求
上述的过程只是标准的、友好的rtsp流程,但实际的需求中并不一定按此过程。
其中第三和第四步是必需的!第一步,只要服务器和客户端约定好有哪些方法可用,则option请求可以不要。第二步,如果我们有其他途径得到媒体初始化描述信息(比如http请求等等),则我们也不需要通过rtsp中的describe请求来完成。
本文接下来介绍rtspserver端的具体实现流程,首先看一下,rtsp交互包的过程
OPTIONS rtsp***** RTSP/1.0CSeq: 2User-Agent: LibVLC/1.1.5 (LIVE555 Streaming Media v2010.09.25)RTSP/1.0 200 OKCSeq: 2Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE,GET_PARAMETERDESCRIBE rtsp:**** RTSP/1.0CSeq: 3User-Agent: LibVLC/1.1.5 (LIVE555 Streaming Media v2010.09.25)Accept: application/sdpRTSP/1.0 200 OKCSeq: 3Server: RTSP ServiceContent-Base: rtsp:****Content-Type: application/sdpContent-Length: 449v=0s=liveplayc=IN IP4 172.21.18.61t=0 0a=control:*m=video 0 RTP/AVP 96a=rtpmap:96 H264/90000a=fmtp:96 profile-level-id=42401E; sprop-parameter-sets=Z0JAHqbBQfoQAAADABAAAAMCgPECLg==,aMpDiA==; packetization-mode=1;a=control:trackID=1m=audio 0 RTP/AVP 98a=rtpmap:98 MPEG4-GENERIC/48000/2a=fmtp:98 streamtype=5; profile-level-id=1; mode=AAC-hbr; config=1190; SizeLength=13; IndexLength=3; IndexDeltaLength=3;a=control:trackID=2SETUP rtsp:***/trackID=1 RTSP/1.0CSeq: 4User-Agent: LibVLC/1.1.5 (LIVE555 Streaming Media v2010.09.25)Transport: RTP/AVP;unicast;client_port=51546-51547RTSP/1.0 200 OKCSeq: 4Session: 1884;timeout=10Transport: RTP/AVP;unicast;server_port=8000-8001;client_port=0-1;ssrc=1SETUP rtsp://192.168.1.0/****/trackID=2 RTSP/1.0CSeq: 5User-Agent: LibVLC/1.1.5 (LIVE555 Streaming Media v2010.09.25)Transport: RTP/AVP;unicast;client_port=51548-51549Session: 1884RTSP/1.0 200 OKCSeq: 5Session: 1884;timeout=10Transport: RTP/AVP;unicast;server_port=8002-8003;client_port=0-1;ssrc=1PLAY rtsp://192.168.0.1/**** RTSP/1.0CSeq: 6User-Agent: LibVLC/1.1.5 (LIVE555 Streaming Media v2010.09.25)Session: 1884Range: npt=0.000-RTSP/1.0 200 OKCSeq: 6Session: 1884;timeout=10RTP-Info: url=rtsp://192.168.1.0/***/trackID=2
接下来代码的流程,就是需要将上面的交互流程串联起来,因此需要设计一些类来处理交互过程,
因此,可以从下面入手来看,需要rtspService类来启动server作为入口
需要rtsp类作为基本的socket读写交互单元,需要RtspResponse类来回复rtsp消息
需要RtspSession处理每一个新的连接!需要RtspTransport类来处理与发送的交互!
于是,为了理清每一个先看下基本的交互过程!
从第一篇可以看出,rtspserver是由main函数启动的,会做下面的动作:
g_pRtspService = new RtspService();
这里面会打开:
/ 创建Rtsp Transport对象 g_pRtspService->m_global_config->m_server_port = serverport; g_pRtspService->m_pMediaStream_Video = new MediaStreamH264( "h264" ); g_pRtspService->m_pMediaStream_Audio = new MediaStreamMP4A( "aac" ); g_pRtspService->m_pMediaSession = new MediaSession( "rtspservice" ); g_pRtspService->m_pMediaSession->AddMediaStream( g_pRtspService->m_pMediaStream_Video ); g_pRtspService->m_pMediaSession->AddMediaStream( g_pRtspService->m_pMediaStream_Audio ); g_pRtspService->m_pRtspTransport = new RtspTransport(); g_pRtspService->m_pRtspTransport->AddMediaSession( g_pRtspService->m_pMediaSession ); g_pRtspService->OpenCapautureThread();
在openCapture里面主要为了初始化传输的buffer!
然后我们来看真正的一个打开流程:
class RtspTransport : public MediaSessionList{ public: RtspTransport(); virtual ~RtspTransport( void ); virtual BOOL Open( PCSTR bindIp = "127.0.0.1", UINT bindPort = 554, UINT maxConnects = 10 ); virtual BOOL Close(); void CloseAll(); protected: static void* StartListenThread( PVOID param ); void ListenThread(); string m_BindIp; UINT m_BindPort; UINT m_MaxConnects; SOCKET m_Socket; SOCKADDR_IN m_BindAddr; BOOL m_isOpen; HANDLE m_hListenThread; HANDLE m_isStopListenThread; RtspSessionMap m_RtspSessionMap; public: bool live_play_add_mp3_data( unsigned char* data, unsigned int dataSize, unsigned int timestamp ); bool live_play_add_h264_data( unsigned char* data, unsigned int dataSize ); cycle_buffer m_mp3_buffer; cycle_buffer m_h264_buffer; private: thread_t m_event_loop_thread; int m_exit_event_loop; void CloseThread(); int m_connect_live_num; SockAddrMap m_rtsp_session_sock_addr_map; void ProcessDelRtspSession( int sockfd ); void ProcessDelRtspSessionOther( int sockfd ); void ReleaseAllCap(); bool ProcessReadRtspSession( fd_set& fdsr ); void ProcessNewIncomingRtspSession(int sock_fd, fd_set& fdsr, int& maxsock); void ProcessTimeoutLiveCmdSession(); int FindSameIpFromMap(SockAddrMap& saMap, const char *ip);};
这里面可以看出:
在open函数里面,创建了socket server端,不过这里不够规范,直接在open里面处理,最好还是能够需要下放到socket接口里面来做,从代码可以看出:经过socket->binder->listen之后,启动一个线程,监听
client端来的请求,如下:
//启动rtsp server监听线程 m_exit_event_loop = 1; thread_create( &( this->m_event_loop_thread ), RtspTransport::StartListenThread, this );
然后,根据刚才的sockfd,通过select系统调用看是否对fd有读写,然后进行accept监听,
来一个connect请求的时候,就会创建一个rstpSession
//设置keepalive的标志的 SetSockeKeepAlive(connect); RtspSession* rtspSession = new RtspSession( connect, m_BindAddr, client_addr, ( MediaSessionList* )this ); m_RtspSessionMap[new_fd] = rtspSession; m_rtsp_session_sock_addr_map[new_fd] = inet_ntoa( client_addr.sin_addr );然后接下来,在RtspSessionThread里面处理交互的过程,如下: if ( !m_pRtspResponse->GetRequests() ) { return -1; } if ( !m_pRtspResponse->GetRequestType( &requestType ) ) { return -1; } requestTypein = requestType ; switch ( requestType ) { case requestOptions: m_time_out = GetTimeHeartbeatSec(); ResponseOptions(); break; case requestDescribe: requestTypein = requestDescribe; { m_time_out = GetTimeHeartbeatSec(); if ( ResponseDescribe() != 0 ) { return -1; } break; } case requestSetup: m_time_out = GetTimeHeartbeatSec(); ResponseSetup(); break; case requestPlay: { m_time_out = GetTimeHeartbeatSec(); ResponsePlay(); break; } case requestPause: { m_time_out = GetTimeHeartbeatSec(); ResponsePause(); break; } case requestTeardown: { m_time_out = GetTimeHeartbeatSec(); //ResponseTeardown(); //停留一些时间,然后需要让对方进行处理的 usleep( 100 * 1000 ); return -1; } case requestGetParameter: ResponseGetParameter(); } break;
这里面的参数需要通过最前面的可以查询出来,因此看看是怎么样跑的!
第一:从GetRequests函数里面拿,这里调用了Rtsp::Read里面读取数据,然后push到
vector<string> m_Requests里面。
第二:在GetRequestType函数里面拿到对应的参数,通过find匹配字符串,然后获取字段
第三:获取字段之后,然后通过rtsp的过程,这时候就首先回应requestOptions
===========================================分割========================================
那么就会来到,RtspSession::ResponseOptions(),进一步到,RtspResponse::ResponseOptions()
于是,构造回应的字符串就ok了,放到rtsp类的vector<string> m_Fields;
方便后面发送做准备,跟着调用,RtspResponse::SendResponse
那么可以看到,
Write( responseCmd.c_str() ); Write( cseq ); if ( m_Session > 0 ) { Write( session ); } WriteFields(); Write( "" );
那么可以看到,
int Rtsp::Write( string str ){ int iWrite; Tcp::Write( ( PBYTE )"\r\n", 2 ); return iWrite;}
通过tcp连接,一次一次发送对应的字符段,让client接收解析并处理,
于是option的交互过程,就算完成!
===========================================分割线===========================
MediaSession* pMediaSession = m_pMediaSessionList->FindMediaSession( sessionName.c_str() ); if ( !pMediaSession ) { m_pRtspResponse->ResponseError( "RTSP/1.0 404 File Url Not Found, Or In Incorrect Format" ); return FALSE; } m_SessionName = sessionName; //QuitTime SETUP时的路径不包含session name 所以预先保存下来 string localIpAddr = inet_ntoa( m_pRtspResponse->GetBindAddr().sin_addr ); string targetIpAddr = inet_ntoa( m_pRtspResponse->GetConnectAddr().sin_addr ); string sdp = pMediaSession->GenerateMediaSdp( localIpAddr.c_str(), targetIpAddr.c_str(), TRUE ); if ( !sdp.length() ) { return FALSE; } // 清空m_SetupUrl 准备在SETUP时保存url地址 m_SetupUrl = ""; if ( describe ) { *describe = sdp; }
于是接下来,进入到describe的流程,直接看,这里面做了很多事情,
TransportDescribe( &sdp )
需要传送sdp,上面主要为了获得SDP交互参数
核心在于生成sdp里面,晚点有详细介绍sdp协议,
这里看代码,可以看到:
mediaSdp += "v=0\r\n"; // v=0 sdp版本 //mediaSdp += "o=- "+sessionId+" "+sessionVersion+" "+"IN IP4 "+localIpAddr+"\r\n"; //- 无用户 IN Internet IP4 ip地址类型 mediaSdp += "s=" + sessionName + "\r\n"; //mediaSdp += "b=AS:50\r\n"; mediaSdp += "c=IN IP4 " + string( targetIpAddr ) + "\r\n"; mediaSdp += "t=" + startTime + " " + stopTime + "\r\n"; //mediaSdp += "a=x-broadcastcontrol:RTSP\r\n"; if ( bUseRTSP ) { mediaSdp += "a=control:*\r\n"; }
主要是为了构造字符串,让client读取,然后确定相互之间的通信方式,播放媒体等一系列的详细信息给client,
然后通过 m_pRtspResponse->ResponseDescribe(sdp.c_str(), sdp.length() );
传送出去,如下,构造字符串,
server = "Server: RTSP Service"; contentType = "Content-Type: application/sdp"; snprintf( temp, 20, "%lu", sdpLength ); contentLength = "Content-Length: "; contentLength += temp; GetRequestMrl( &requestMrl ); contentBase = "Content-Base: "; contentBase += requestMrl; AddField( server ); AddField( contentBase ); AddField( contentType ); AddField( contentLength ); SendResponse( "" ); Tcp::Write( ( PBYTE )sdp, sdpLength );===========================================分割线========================================
接下来就走到了setup流程,如下,
if ( !TransportSetup( &serverIp, &serverPort, &targetIp, &targetPort, &ssrc ) ) { return; } m_pRtspResponse->ResponseSetup( serverIp.c_str(), serverPort, targetIp.c_str(), targetPort, ssrc );
很明显,这里传递ip以及port,通过下面实现,
//使用trackID设置stream string::size_type iFind = streamName.find( "trackID=" ); if ( iFind != string::npos ) { streamName.erase( iFind, 8 ); int streamIdx = atoi( streamName.c_str() ) - 1; pTargetPort = &streamIdx; pMediaStream = pMediaSession->GetMediaStream( streamIdx ); if ( !pMediaStream ) { m_pRtspResponse->ResponseError( "RTSP/1.0 404 Stream Not Found. pMediaStream" ); return FALSE; } return StreamSetup( pMediaStream, pBindIp, pBindPort, pTargetIp, pTargetPort ); }
这里通过trackID来传递,真正的Rtp StreamSetup流的过程,
if ( !m_pRtspResponse->GetRequestTransportInfo( &targetPort, NULL ) )
拿到client端的port,然后,拿到ip之后,需要传送出去,
这里面为了和传送保持一致,将值也给了发送线程,
Global_Config* pconfig = Global_Config::instance(); bindPort = pconfig->m_ts_local_rtp_port; TSRtpTransportSession* ptstransport = TSRtpTransportSession::instance(); ptstransport->m_local_port = bindPort; ptstransport ->add_dest_socket_info( ( RTSP_SESSION_HANDLE_TS ) this, targetPort, targetIp );
于是进一步,发送了setup的流程,
snprintf( temp, 100, "server_port=%u-%u", serverRtpPort, serverRtpPort + 1 ); server_port = temp; snprintf( temp, 100, "client_port=%u-%u", targetRtpPort, targetRtpPort + 1 ); client_port = temp; snprintf( temp, 100, "ssrc=%u", ssrc ); ssrc_ = temp; transport += "Transport: RTP/AVP;unicast;"; transport += server_port; transport += ';'; transport += client_port; transport += ';'; transport += ssrc_; AddField( transport ); SendResponse( "" );
===========================================分割线========================================
接下来,进入到play的流程,这里是整体架构的核心点,发送端采取:
void RtspResponse::ResponsePlay( PCSTR setupUrl ){ string rtpinfo = "RTP-Info: "; rtpinfo += setupUrl; AddField( rtpinfo ); SendResponse( "" );}
但是在rtsptransport里面会根据刚才的type类型做过滤,
然后收到play的流程,那么说明,交互都ok了,需要进入下一步了,
采集和发送数据了,如下,
if ( requesttypein == requestPlay && m_connect_live_num == 1 ) { //设置发送UDP数据的源IP struct sockaddr_in localaddr; socklen_t locallen = sizeof( localaddr ); getsockname( sockfd, ( struct sockaddr * )&localaddr, &locallen ); 抓取和发送视频的回调函数 }
于是rtsp的交互分析完了,至于细节单独分析,请见下面的文章!
- 分析一个通用的rtsp server实现过程串联模块
- 分析一个通用的rtsp server实现过程基础模块
- 分析一个通用的rtsp server实现过程发送模块
- 分析一个通用的rtsp server实现采集模块
- 分析一个通用的rtsp server实现过程总纲
- go: 一个通用log模块的实现
- Android 二级串联菜单的实现过程
- SQL Server 一个比较通用的分页存储过程
- SQL Server通用分页存储过程:利用SQL Server未公开的存储过程实现
- SQL Server通用分页存储过程:利用SQL Server未公开的存储过程实现
- SQL Server通用分页存储过程:利用SQL Server未公开的存储过程实现
- RTSP服务器端实现的一个问题
- Sql Server通用的分页存储过程
- 简单完整的一个 rtsp 交互过程 live555-openRTSP
- 一个通用dao的实现
- 一个通用dao的实现
- IEditableObject的一个通用实现
- 一个通用的存储过程调用类
- jmeter 关联
- FastDFS文件系统
- QuickTime文件格式规范
- com.android.ddmlib.AdbCommandRejectedException: insufficient permissions for device: verify udev rul
- 彻底地摆脱Ubuntu中的系统错误弹窗
- 分析一个通用的rtsp server实现过程串联模块
- Python学习(字符串操作)
- VS2010生成安装包制作步骤
- exec (@sql) 接收返回值
- 初次加载tomcat,Server Tomcat v8.0 Server at localhost failed to start
- 【51nod 1288 汽油补给 】 贪心 & 思维
- 抓包工具
- SpringBoot的RabbitMQ消息队列: 一、消息发送接收第一印象
- jquery源码解析之选择器实现