分析一个通用的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请求来完成。

上述就是一个简单的rtsp交互的信息,从http://www.2cto.com/net/201607/526974.html截取!

本文接下来介绍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的交互分析完了,至于细节单独分析,请见下面的文章!

阅读全文
0 0
原创粉丝点击