Beispiel #1
0
/**
* @brief 
*
* @return 
*/
int HttpStream::run()
{
    Select select( 60 );
    select.addWriter( mConnection->socket() );

    if ( !waitForProviders() )
        return( 1 );

    while ( !mStop && select.wait() >= 0 )
    {
        if ( mStop )
           break;
        Select::CommsList writeable = select.getWriteable();
        if ( writeable.size() <= 0 )
        {
            Error( "Writer timed out" );
            mStop = true;
            break;
        }
        mQueueMutex.lock();
        if ( !mFrameQueue.empty() )
        {
            for ( FrameQueue::iterator iter = mFrameQueue.begin(); iter != mFrameQueue.end(); iter++ )
            {
                sendFrame( writeable, *iter );
                //delete *iter;
            }
            mFrameQueue.clear();
        }
        mQueueMutex.unlock();
        usleep( INTERFRAME_TIMEOUT );
    }

    return( 0 );
}
int RtpDataThread::run()
{
  Debug( 2, "Starting data thread %d on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalDataPort() );

  SockAddrInet localAddr;
  UdpInetServer rtpDataSocket;
  if ( mRtpSource.getLocalHost() != "" ) {
    if ( !rtpDataSocket.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() ) )
      Fatal( "Failed to bind RTP server" );
    Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() );
  }
  else
  {
    if ( !rtpDataSocket.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalDataPort() ) )
      Fatal( "Failed to bind RTP server" );
    Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort() );
  }

  Select select( 3 );
  select.addReader( &rtpDataSocket );

  unsigned char buffer[ZM_NETWORK_BUFSIZ];
  while ( !mStop && select.wait() >= 0 )
  {
     if ( mStop )
      break;
     Select::CommsList readable = select.getReadable();
     if ( readable.size() == 0 )
     {
       Error( "RTP timed out" );
       mStop = true;
       break;
     }
     for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); iter++ )
     {
       if ( UdpInetServer *socket = dynamic_cast<UdpInetServer *>(*iter) )
       {
         int nBytes = socket->recv( buffer, sizeof(buffer) );
         Debug( 4, "Got %d bytes on sd %d", nBytes, socket->getReadDesc() );
         if ( nBytes )
         {
            recvPacket( buffer, nBytes );
         }
         else
         {
          mStop = true;
          break;
         }
       }
       else
       {
         Panic( "Barfed" );
       }
     }
  }
  rtpDataSocket.close();
  mRtspThread.stop();
  return( 0 );
}
Beispiel #3
0
int RtspThread::run()
{
    std::string message;
    std::string response;

    response.reserve( ZM_NETWORK_BUFSIZ );

    if ( !mRtspSocket.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
        Fatal( "Unable to connect RTSP socket" );
    //Select select( 0.25 );
    //select.addReader( &mRtspSocket );
    //while ( select.wait() )
    //{
        //mRtspSocket.recv( response );
        //Debug( 4, "Drained %d bytes from RTSP socket", response.size() );
    //}

    
    bool authTried = false; 
    if ( mMethod == RTP_RTSP_HTTP )
    {
        if ( !mRtspSocket2.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
            Fatal( "Unable to connect auxiliary RTSP/HTTP socket" );
        //Select select( 0.25 );
        //select.addReader( &mRtspSocket2 );
        //while ( select.wait() )
        //{
            //mRtspSocket2.recv( response );
            //Debug( 4, "Drained %d bytes from HTTP socket", response.size() );
        //}
        
        //possibly retry sending the message for authentication 
        int respCode = -1;
        char respText[256];
        do {
			message = "GET "+mPath+" HTTP/1.0\r\n";
			message += "X-SessionCookie: "+mHttpSession+"\r\n";
			if ( mNeedAuth ) {
				message += mAuthenticator->getAuthHeader("GET", mPath);
				authTried = true;
			}
		    message += "Accept: application/x-rtsp-tunnelled\r\n";
			message += "\r\n";
			Debug( 2, "Sending HTTP message: %s", message.c_str() );
			if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() )
			{
				Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
				return( -1 );
			}
			if ( mRtspSocket.recv( response ) < 0 )
			{
				Error( "Recv failed; %s", strerror(errno) );
				return( -1 );
			}
		
			Debug( 2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size() );
			float respVer = 0;
			respCode = -1;
			if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 )
			{
				if ( isalnum(response[0]) )
				{
				Error( "Response parse failure in '%s'", response.c_str() );
				}
				else
				{
				Error( "Response parse failure, %zd bytes follow", response.size() );
				if ( response.size() )
					Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
				}
				return( -1 );
			}
			// If Server requests authentication, check WWW-Authenticate header and fill required fields
			// for requested authentication method
			if (respCode == 401 && !authTried) {
				mNeedAuth = true;
				mAuthenticator->checkAuthResponse(response);
				Debug(2, "Processed 401 response");
				mRtspSocket.close();
			    if ( !mRtspSocket.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
			        Fatal( "Unable to reconnect RTSP socket" );
			    Debug(2, "connection should be reopened now");
			}
			
		} while (respCode == 401 && !authTried);  
		
        if ( respCode != 200 )
        {
            Error( "Unexpected response code %d, text is '%s'", respCode, respText );
            return( -1 );
        }

        message = "POST "+mPath+" HTTP/1.0\r\n";
        message += "X-SessionCookie: "+mHttpSession+"\r\n";
		if ( mNeedAuth )
			message += mAuthenticator->getAuthHeader("POST", mPath);
        message += "Content-Length: 32767\r\n";
        message += "Content-Type: application/x-rtsp-tunnelled\r\n";
        message += "\r\n";
        Debug( 2, "Sending HTTP message: %s", message.c_str() );
        if ( mRtspSocket2.send( message.c_str(), message.size() ) != (int)message.length() )
        {
            Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
            return( -1 );
        }
    }

    std::string localHost = "";
    int localPorts[2] = { 0, 0 };

    // Request supported RTSP commands by the server
    message = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
    if ( !sendCommand( message ) )
        return( -1 );

	// A negative return here may indicate auth failure, but we will have setup the auth mechanisms so we need to retry.
    if ( !recvResponse( response ) ) {
		if ( mNeedAuth ) {
			Debug( 2, "Resending OPTIONS due to possible auth requirement" );
			if ( !sendCommand( message ) )
				return( -1 );
			if ( !recvResponse( response ) )
				return( -1 );
		} else {
			return( -1 );
		}
	} // end if failed response maybe due to auth

    char publicLine[256] = "";
    StringVector lines = split( response, "\r\n" );
    for ( size_t i = 0; i < lines.size(); i++ )
        sscanf( lines[i].c_str(), "Public: %[^\r\n]\r\n", publicLine );

    // Check if the server supports the GET_PARAMETER command
    // If yes, it is likely that the server will request this command as a keepalive message
    bool sendKeepalive = false;
    if ( publicLine[0] && strstr(publicLine, "GET_PARAMETER") )
        sendKeepalive = true;

    message = "DESCRIBE "+mUrl+" RTSP/1.0\r\n";
	bool res;
	do {
		if (mNeedAuth)
			authTried = true;
		sendCommand( message );
		sleep( 1 );
		res = recvResponse( response );
		if (!res && respCode==401)
			mNeedAuth = true;
	} while (!res && respCode==401 && !authTried);

    const std::string endOfHeaders = "\r\n\r\n";
    size_t sdpStart = response.find( endOfHeaders );
    if( sdpStart == std::string::npos )
        return( -1 );

    std::string DescHeader = response.substr( 0,sdpStart );
    Debug( 1, "Processing DESCRIBE response header '%s'", DescHeader.c_str() );

    lines = split( DescHeader, "\r\n" );
    for ( size_t i = 0; i < lines.size(); i++ )
    	{
    		// If the device sends us a url value for Content-Base in the response header, we should use that instead
    		if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) )
    			{
    				mUrl = trimSpaces( lines[i].substr( 13 ) );
    				Info("Received new Content-Base in DESCRIBE response header. Updated device Url to: '%s'", mUrl.c_str() );
    				break;
    			}
    	}

    sdpStart += endOfHeaders.length();

    std::string sdp = response.substr( sdpStart );
    Debug( 1, "Processing SDP '%s'", sdp.c_str() );

    try
    {
        mSessDesc = new SessionDescriptor( mUrl, sdp );
        mFormatContext = mSessDesc->generateFormatContext();
    }
    catch( const Exception &e )
    {
        Error( e.getMessage().c_str() );
        return( -1 );
    }

#if 0
    // New method using ffmpeg native functions
    std::string authUrl = mUrl;
    if ( !mAuth.empty() )
        authUrl.insert( authUrl.find( "://" )+3, mAuth+"@" );

    if ( av_open_input_file( &mFormatContext, authUrl.c_str(), NULL, 0, NULL ) != 0 )
    {
        Error( "Unable to open input '%s'", authUrl.c_str() );
        return( -1 );
    }
#endif

    uint32_t rtpClock = 0;
    std::string trackUrl = mUrl;
    std::string controlUrl;
    
    _AVCODECID codecId;
    
    if ( mFormatContext->nb_streams >= 1 )
    {
        for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ )
        {
            SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream( i );
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
            if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
#else
            if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
#endif
            {
                // Check if control Url is absolute or relative
                controlUrl = mediaDesc->getControlUrl();
                if (std::equal(trackUrl.begin(), trackUrl.end(), controlUrl.begin()))
                {
                    trackUrl = controlUrl;
                }
                else
                {
					if ( *trackUrl.rbegin() != '/') {
						trackUrl += "/" + controlUrl;
					} else {
						trackUrl += controlUrl;
					}
                }
                rtpClock = mediaDesc->getClock();
                codecId = mFormatContext->streams[i]->codec->codec_id;
                // Hackery pokery
                //rtpClock = mFormatContext->streams[i]->codec->sample_rate;
                break;
            }
        }
    }

    switch( mMethod )
    {
        case RTP_UNICAST :
        {
            localPorts[0] = requestPorts();
            localPorts[1] = localPorts[0]+1;

            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n";
            break;
        }
        case RTP_MULTICAST :
        {
            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n";
            break;
        }
        case RTP_RTSP :
        case RTP_RTSP_HTTP :
        {
            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n";
            break;
        }
        default:
        {
            Panic( "Got unexpected method %d", mMethod );
            break;
        }
    }

    if ( !sendCommand( message ) )
        return( -1 );
    if ( !recvResponse( response ) )
        return( -1 );

    lines = split( response, "\r\n" );
    std::string session;
    int timeout = 0;
    char transport[256] = "";

    for ( size_t i = 0; i < lines.size(); i++ )
    {
        if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) )
        {
            StringVector sessionLine = split( lines[i].substr(9), ";" );
            session = trimSpaces( sessionLine[0] );
            if ( sessionLine.size() == 2 )
                sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout );
        }
        sscanf( lines[i].c_str(), "Transport: %s", transport );
    }

    if ( session.empty() )
        Fatal( "Unable to get session identifier from response '%s'", response.c_str() );

    Debug( 2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout );

    if ( !transport[0] )
        Fatal( "Unable to get transport details from response '%s'", response.c_str() );

    Debug( 2, "Got RTSP transport %s", transport );

    std::string method = "";
    int remotePorts[2] = { 0, 0 };
    int remoteChannels[2] = { 0, 0 };
    std::string distribution = "";
    unsigned long ssrc = 0;
    StringVector parts = split( transport, ";" );
    for ( size_t i = 0; i < parts.size(); i++ )
    {
        if ( parts[i] == "unicast" || parts[i] == "multicast" )
            distribution = parts[i];
        else if ( startsWith( parts[i], "server_port=" ) )
        {
            method = "RTP/UNICAST";
            StringVector subparts = split( parts[i], "=" );
            StringVector ports = split( subparts[1], "-" );
            remotePorts[0] = strtol( ports[0].c_str(), NULL, 10 );
            remotePorts[1] = strtol( ports[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "interleaved=" ) )
        {
            method = "RTP/RTSP";
            StringVector subparts = split( parts[i], "=" );
            StringVector channels = split( subparts[1], "-" );
            remoteChannels[0] = strtol( channels[0].c_str(), NULL, 10 );
            remoteChannels[1] = strtol( channels[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "port=" ) )
        {
            method = "RTP/MULTICAST";
            StringVector subparts = split( parts[i], "=" );
            StringVector ports = split( subparts[1], "-" );
            localPorts[0] = strtol( ports[0].c_str(), NULL, 10 );
            localPorts[1] = strtol( ports[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "destination=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            localHost = subparts[1];
        }
        else if ( startsWith( parts[i], "ssrc=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            ssrc = strtoll( subparts[1].c_str(), NULL, 16 );
        }
    }

    Debug( 2, "RTSP Method is %s", method.c_str() );
    Debug( 2, "RTSP Distribution is %s", distribution.c_str() );
    Debug( 2, "RTSP SSRC is %lx", ssrc );
    Debug( 2, "RTSP Local Host is %s", localHost.c_str() );
    Debug( 2, "RTSP Local Ports are %d/%d", localPorts[0], localPorts[1] );
    Debug( 2, "RTSP Remote Ports are %d/%d", remotePorts[0], remotePorts[1] );
    Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] );

    message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n";
    if ( !sendCommand( message ) )
        return( -1 );
    if ( !recvResponse( response ) )
        return( -1 );

    lines = split( response, "\r\n" );
    std::string rtpInfo;
    for ( size_t i = 0; i < lines.size(); i++ )
    {
        if ( ( lines[i].size() > 9 ) && ( lines[i].substr( 0, 9 ) == "RTP-Info:" ) )
            rtpInfo = trimSpaces( lines[i].substr( 9 ) );
	// Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent
        if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) && ( timeout == 0 ) )
        {
            StringVector sessionLine = split( lines[i].substr(9), ";" );
            if ( sessionLine.size() == 2 )
                sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout );
            if ( timeout > 0 )
                Debug( 2, "Got timeout %d secs from PLAY command response", timeout );
        }
    }

    int seq = 0;
    unsigned long rtpTime = 0;
    StringVector streams;
    if ( rtpInfo.empty() )
    {
        Debug( 1, "RTP Info Empty. Starting values for Sequence and Rtptime shall be zero.");
    }
    else
    {
        Debug( 2, "Got RTP Info %s", rtpInfo.c_str() );
        // More than one stream can be included in the RTP Info
        streams = split( rtpInfo.c_str(), "," );
        for ( size_t i = 0; i < streams.size(); i++ )
        {
            // We want the stream that matches the trackUrl we are using
            if ( streams[i].find(controlUrl.c_str()) != std::string::npos )
            {
                // Parse the sequence and rtptime values
                parts = split( streams[i].c_str(), ";" );
                for ( size_t j = 0; j < parts.size(); j++ )
                {
                    if ( startsWith( parts[j], "seq=" ) )
                    {
                        StringVector subparts = split( parts[j], "=" );
                        seq = strtol( subparts[1].c_str(), NULL, 10 );
                    }
                    else if ( startsWith( parts[j], "rtptime=" ) )
                    {
                        StringVector subparts = split( parts[j], "=" );
                        rtpTime = strtol( subparts[1].c_str(), NULL, 10 );
                    }
                }
            break;
            }
        }
    }

    Debug( 2, "RTSP Seq is %d", seq );
    Debug( 2, "RTSP Rtptime is %ld", rtpTime );

    time_t lastKeepalive = time(NULL);
	time_t now;
    message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";

    switch( mMethod )
    {
        case RTP_UNICAST :
        {
            RtpSource *source = new RtpSource( mId, "", localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            rtpDataThread.start();
            rtpCtrlThread.start();

            while( !mStop )
            {
				now = time(NULL);
                // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepalive, timeout, now, lastKeepalive, (now-lastKeepalive) );
                if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) )
                {
                    if ( !sendCommand( message ) )
                        return( -1 );
                    lastKeepalive = now;
                }
                usleep( 100000 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif

            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );

            rtpDataThread.stop();
            rtpCtrlThread.stop();

            //rtpDataThread.kill( SIGTERM );
            //rtpCtrlThread.kill( SIGTERM );

            rtpDataThread.join();
            rtpCtrlThread.join();
         
            delete mSources[ssrc];
            mSources.clear();

            releasePorts( localPorts[0] );

            break;
        }
        case RTP_RTSP :
        case RTP_RTSP_HTTP :
        {
            RtpSource *source = new RtpSource( mId, "", remoteChannels[0], mHost, remoteChannels[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            // These never actually run
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            Select select( double(config.http_timeout)/1000.0 );
            select.addReader( &mRtspSocket );

            Buffer buffer( ZM_NETWORK_BUFSIZ );
            std::string keepaliveMessage = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
            std::string keepaliveResponse = "RTSP/1.0 200 OK\r\n";
            while ( !mStop && select.wait() >= 0 )
            {
                Select::CommsList readable = select.getReadable();
                if ( readable.size() == 0 )
                {
                    Error( "RTSP timed out" );
                    break;
                }

                static char tempBuffer[ZM_NETWORK_BUFSIZ];
                ssize_t nBytes = mRtspSocket.recv( tempBuffer, sizeof(tempBuffer) );
                buffer.append( tempBuffer, nBytes );
                Debug( 4, "Read %zd bytes on sd %d, %d total", nBytes, mRtspSocket.getReadDesc(), buffer.size() );

                while( buffer.size() > 0 )
                {
                    if ( buffer[0] == '$' )
                    {
                	if ( buffer.size() < 4 )
                	    break;
                        unsigned char channel = buffer[1];
                        unsigned short len = ntohs( *((unsigned short *)(buffer+2)) );

                        Debug( 4, "Got %d bytes left, expecting %d byte packet on channel %d", buffer.size(), len, channel );
                        if ( (unsigned short)buffer.size() < (len+4) )
                        {
                            Debug( 4, "Missing %d bytes, rereading", (len+4)-buffer.size() );
                            break;
                        }
                        if ( channel == remoteChannels[0] )
                        {
                            Debug( 4, "Got %d bytes on data channel %d, packet length is %d", buffer.size(), channel, len );
                            Hexdump( 4, (char *)buffer, 16 );
                            rtpDataThread.recvPacket( buffer+4, len );
                            Debug( 4, "Received" );
                        }
                        else if ( channel == remoteChannels[1] )
                        {
//                            len = ntohs( *((unsigned short *)(buffer+2)) );
//                            Debug( 4, "Got %d bytes on control channel %d", nBytes, channel );
                            Debug( 4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len );
                            Hexdump( 4, (char *)buffer, 16 );
                            rtpCtrlThread.recvPackets( buffer+4, len );
                        }
                        else
                        {
                            Error( "Unexpected channel selector %d in RTSP interleaved data", buffer[1] );
                            buffer.clear();
                            break;
                        }
                        buffer.consume( len+4 );
                        nBytes -= len+4;
                    }
                    else
                    {
                        if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 )
                        {
                            Debug( 4, "Got keepalive response '%s'", (char *)buffer );
                            //buffer.consume( keepaliveResponse.size() );
                            if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
                            {
                                int discardBytes = charPtr-(char *)buffer;
                                buffer -= discardBytes;
                            }
                            else
                            {
                                buffer.clear();
                            }
                        }
                        else
                        {
                            if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
                            {
                                int discardBytes = charPtr-(char *)buffer;
                                Warning( "Unexpected format RTSP interleaved data, resyncing by %d bytes", discardBytes );
                                Hexdump( -1, (char *)buffer, discardBytes );
                                buffer -= discardBytes;
                            }
                            else
                            {
                                Warning( "Unexpected format RTSP interleaved data, dumping %d bytes", buffer.size() );
                                Hexdump( -1, (char *)buffer, 32 );
                                buffer.clear();
                            }
                        }
                    }
                }
                // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
                // FIXME: Is this really necessary when using tcp ?
				now = time(NULL);
                // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepalive, timeout, now, lastKeepalive, (now-lastKeepalive) );
                if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) )
                {
                    if ( !sendCommand( message ) )
                        return( -1 );
                    lastKeepalive = now;
                }
                buffer.tidy( 1 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif
            // Send a teardown message but don't expect a response as this may not be implemented on the server when using TCP
            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );

            delete mSources[ssrc];
            mSources.clear();

            break;
        }
        case RTP_MULTICAST :
        {
            RtpSource *source = new RtpSource( mId, localHost, localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            rtpDataThread.start();
            rtpCtrlThread.start();

            while( !mStop )
            {
                // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
                if ( sendKeepalive && (timeout > 0) && ((time(NULL)-lastKeepalive) > (timeout-5)) )
                {
                    if ( !sendCommand( message ) )
                        return( -1 );
                    lastKeepalive = time(NULL);
                }
                usleep( 100000 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif
            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );

            rtpDataThread.stop();
            rtpCtrlThread.stop();

            rtpDataThread.join();
            rtpCtrlThread.join();
         
            delete mSources[ssrc];
            mSources.clear();

            releasePorts( localPorts[0] );
            break;
        }
        default:
        {
            Panic( "Got unexpected method %d", mMethod );
            break;
        }
    }

    return( 0 );
}
Beispiel #4
0
int RtpCtrlThread::run()
{
    Debug( 2, "Starting control thread %lx on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() );
    SockAddrInet localAddr, remoteAddr;

    bool sendReports;
    UdpInetSocket rtpCtrlServer;
    if ( mRtpSource.getLocalHost() != "" )
    {
        localAddr.resolve( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort(), "udp" );
        if ( !rtpCtrlServer.bind( localAddr ) )
            Fatal( "Failed to bind RTCP server" );
        sendReports = false;
        Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
    }
    else
    {
        localAddr.resolve( mRtpSource.getLocalCtrlPort(), "udp" );
        if ( !rtpCtrlServer.bind( localAddr ) )
            Fatal( "Failed to bind RTCP server" );
        Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
        remoteAddr.resolve( mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort(), "udp" );
        if ( !rtpCtrlServer.connect( remoteAddr ) )
            Fatal( "Failed to connect RTCP server" );
        Debug( 3, "Connected to %s:%d",  mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort() );
        sendReports = true;
    }

    Select select( 10 );
    select.addReader( &rtpCtrlServer );

    unsigned char buffer[BUFSIZ];
    while ( !mStop && select.wait() >= 0 )
    {
        if ( mStop )
            break;
        Select::CommsList readable = select.getReadable();
        if ( readable.size() == 0 )
        {
            Error( "RTCP timed out" );
            break;
        }
        for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); iter++ )
        {
            if ( UdpInetSocket *socket = dynamic_cast<UdpInetSocket *>(*iter) )
            {
                ssize_t nBytes = socket->recv( buffer, sizeof(buffer) );
                Debug( 4, "Read %d bytes on sd %d", nBytes, socket->getReadDesc() );

                if ( nBytes )
                {
                    recvPackets( buffer, nBytes );

                    if ( sendReports )
                    {
                        unsigned char *bufferPtr = buffer;
                        bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
                        bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
                        Debug( 4, "Sending %d bytes on sd %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc() );
                        if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
                            Error( "Unable to send: %s", strerror( errno ) );
                        //Debug( 4, "Sent %d bytes on sd %d", nBytes, rtpCtrlServer.getWriteDesc() );
                    }
                }
                else
                {
                    mStop = true;
                    break;
                }
            }
            else
            {
                Fatal( "Barfed" );
            }
        }
    }
    rtpCtrlServer.close();
    mRtspThread.stop();
    return( 0 );
}
Beispiel #5
0
int RtpCtrlThread::run() {
  Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() );
  SockAddrInet localAddr, remoteAddr;

  bool sendReports;
  UdpInetSocket rtpCtrlServer;
  if ( mRtpSource.getLocalHost() != "" ) {
    if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) )
      Fatal( "Failed to bind RTCP server" );
    sendReports = false;
    Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
  } else {
    if ( !rtpCtrlServer.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalCtrlPort() ) )
      Fatal( "Failed to bind RTCP server" );
    Debug( 3, "Bound to %s:%d",  mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
    if ( !rtpCtrlServer.connect( mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort() ) )
      Fatal( "Failed to connect RTCP server" );
    Debug( 3, "Connected to %s:%d",  mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort() );
    sendReports = true;
  }

  // The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets.
  // Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response
  Select select( 10 );
  select.addReader( &rtpCtrlServer );

  unsigned char buffer[ZM_NETWORK_BUFSIZ];

  time_t  last_receive = time(NULL);
  bool  timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true.

  while ( !mStop && select.wait() >= 0 ) {

    time_t now = time(NULL);
    Select::CommsList readable = select.getReadable();
    if ( readable.size() == 0 ) {
      if ( ! timeout ) {
        // With this code here, we will send an SDES and RR packet every 10 seconds
        ssize_t nBytes;
        unsigned char *bufferPtr = buffer;
        bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
        bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
        Debug( 3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %d",
            bufferPtr-buffer, rtpCtrlServer.getWriteDesc(), ( now-last_receive) );
        if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 )
          Error("Unable to send: %s", strerror(errno));
        timeout = true;
        continue;
      } else {
        //Error( "RTCP timed out" );
        Debug(1, "RTCP timed out. Time since last receive: %d", ( now-last_receive) );
        continue;
        //break;
      }
    } else {
      timeout = false;
      last_receive = time(NULL);
    }
    for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) {
      if ( UdpInetSocket *socket = dynamic_cast<UdpInetSocket *>(*iter) ) {
        ssize_t nBytes = socket->recv( buffer, sizeof(buffer) );
        Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() );

        if ( nBytes ) {
          recvPackets( buffer, nBytes );

          if ( sendReports ) {
            unsigned char *bufferPtr = buffer;
            bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
            bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
            Debug(3, "Sending %zd bytes on sd %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc());
            if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
              Error("Unable to send: %s", strerror(errno));
            //Debug( 4, "Sent %d bytes on sd %d", nBytes, rtpCtrlServer.getWriteDesc() );
          }
        } else {
          // Here is another case of not receiving some data causing us to terminate... why?  Sometimes there are pauses in the interwebs.
          mStop = true;
          break;
        }
      } else {
        Panic("Barfed");
      } // end if socket
    } // end foeach comms iterator
  }
  rtpCtrlServer.close();
  mRtspThread.stop();
  return 0;
}
Beispiel #6
0
int RtspThread::run()
{
    std::string message;
    std::string response;

    response.reserve( ZM_NETWORK_BUFSIZ );

    if ( !mRtspSocket.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
        Fatal( "Unable to connect RTSP socket" );
    //Select select( 0.25 );
    //select.addReader( &mRtspSocket );
    //while ( select.wait() )
    //{
        //mRtspSocket.recv( response );
        //Debug( 4, "Drained %d bytes from RTSP socket", response.size() );
    //}

    if ( mMethod == RTP_RTSP_HTTP )
    {
        if ( !mRtspSocket2.connect( mHost.c_str(), strtol( mPort.c_str(), NULL, 10 ) ) )
            Fatal( "Unable to connect auxiliary RTSP/HTTP socket" );
        //Select select( 0.25 );
        //select.addReader( &mRtspSocket2 );
        //while ( select.wait() )
        //{
            //mRtspSocket2.recv( response );
            //Debug( 4, "Drained %d bytes from HTTP socket", response.size() );
        //}

        message = "GET "+mPath+" HTTP/1.0\r\n";
        message += "X-SessionCookie: "+mHttpSession+"\r\n";
        if ( !mAuth.empty() )
            message += stringtf( "Authorization: Basic %s\r\n", mAuth64.c_str() );
        message += "\r\n";
        Debug( 2, "Sending HTTP message: %s", message.c_str() );
        if ( mRtspSocket.send( message.c_str(), message.size() ) != (int)message.length() )
        {
            Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
            return( -1 );
        }
        if ( mRtspSocket.recv( response ) < 0 )
        {
            Error( "Recv failed; %s", strerror(errno) );
            return( -1 );
        }

        Debug( 2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size() );
        float respVer = 0;
        int respCode = -1;
        char respText[256];
        if ( sscanf( response.c_str(), "HTTP/%f %3d %[^\r\n]\r\n", &respVer, &respCode, respText ) != 3 )
        {
            if ( isalnum(response[0]) )
            {
                Error( "Response parse failure in '%s'", response.c_str() );
            }
            else
            {
                Error( "Response parse failure, %zd bytes follow", response.size() );
                if ( response.size() )
                    Hexdump( Logger::ERROR, response.data(), min(response.size(),16) );
            }
            return( -1 );
        }
        if ( respCode != 200 )
        {
            Error( "Unexpected response code %d, text is '%s'", respCode, respText );
            return( -1 );
        }

        message = "POST "+mPath+" HTTP/1.0\r\n";
        message += "X-SessionCookie: "+mHttpSession+"\r\n";
        if ( !mAuth.empty() )
            message += stringtf( "Authorization: Basic %s\r\n", mAuth64.c_str() );
        message += "Content-Length: 32767\r\n";
        message += "Content-Type: application/x-rtsp-tunnelled\r\n";
        message += "\r\n";
        Debug( 2, "Sending HTTP message: %s", message.c_str() );
        if ( mRtspSocket2.send( message.c_str(), message.size() ) != (int)message.length() )
        {
            Error( "Unable to send message '%s': %s", message.c_str(), strerror(errno) );
            return( -1 );
        }
    }

    std::string localHost = "";
    int localPorts[2] = { 0, 0 };

    //message = "OPTIONS * RTSP/1.0\r\n";
    //sendCommand( message );
    //recvResponse( response );

    message = "DESCRIBE "+mUrl+" RTSP/1.0\r\n";
    sendCommand( message );
    sleep( 1 );
    recvResponse( response );

    const std::string endOfHeaders = "\r\n\r\n";
    size_t sdpStart = response.find( endOfHeaders );
    if( sdpStart == std::string::npos )
        return( -1 );
    sdpStart += endOfHeaders.length();

    std::string sdp = response.substr( sdpStart );
    Debug( 1, "Processing SDP '%s'", sdp.c_str() );

    SessionDescriptor *sessDesc = 0;
    try
    {
        sessDesc = new SessionDescriptor( mUrl, sdp );
        mFormatContext = sessDesc->generateFormatContext();
    }
    catch( const Exception &e )
    {
        Error( e.getMessage().c_str() );
        return( -1 );
    }

#if 0
    // New method using ffmpeg native functions
    std::string authUrl = mUrl;
    if ( !mAuth.empty() )
        authUrl.insert( authUrl.find( "://" )+3, mAuth+"@" );

    if ( av_open_input_file( &mFormatContext, authUrl.c_str(), NULL, 0, NULL ) != 0 )
    {
        Error( "Unable to open input '%s'", authUrl.c_str() );
        return( -1 );
    }
#endif

    uint32_t rtpClock = 0;
    std::string trackUrl = mUrl;
    
    #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54,25,0)
    enum AVCodecID codecId;
    #else
    enum CodecID codecId;
    #endif
    
    if ( mFormatContext->nb_streams >= 1 )
    {
        for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ )
        {
            SessionDescriptor::MediaDescriptor *mediaDesc = sessDesc->getStream( i );
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,2,1)
            if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
#else
            if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
#endif
            {
                trackUrl += "/"+mediaDesc->getControlUrl();
                rtpClock = mediaDesc->getClock();
                codecId = mFormatContext->streams[i]->codec->codec_id;
                // Hackery pokery
                //rtpClock = mFormatContext->streams[i]->codec->sample_rate;
                break;
            }
        }
    }

    switch( mMethod )
    {
        case RTP_UNICAST :
        {
            localPorts[0] = requestPorts();
            localPorts[1] = localPorts[0]+1;

            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n";
            break;
        }
        case RTP_MULTICAST :
        {
            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n";
            break;
        }
        case RTP_RTSP :
        case RTP_RTSP_HTTP :
        {
            message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n";
            break;
        }
        default:
        {
            Panic( "Got unexpected method %d", mMethod );
            break;
        }
    }

    if ( !sendCommand( message ) )
        return( -1 );
    if ( !recvResponse( response ) )
        return( -1 );

    StringVector lines = split( response, "\r\n" );
    char *session = 0;
    int timeout = 0;
    char transport[256] = "";

    for ( size_t i = 0; i < lines.size(); i++ )
    {
        sscanf( lines[i].c_str(), "Session: %a[0-9a-fA-F]; timeout=%d", &session, &timeout );
        sscanf( lines[i].c_str(), "Transport: %s", transport );
    }

    if ( !session )
        Fatal( "Unable to get session identifier from response '%s'", response.c_str() );

    Debug( 2, "Got RTSP session %s, timeout %d secs", session, timeout );

    if ( !transport[0] )
        Fatal( "Unable to get transport details from response '%s'", response.c_str() );

    Debug( 2, "Got RTSP transport %s", transport );

    std::string method = "";
    int remotePorts[2] = { 0, 0 };
    int remoteChannels[2] = { 0, 0 };
    std::string distribution = "";
    unsigned long ssrc = 0;
    StringVector parts = split( transport, ";" );
    for ( size_t i = 0; i < parts.size(); i++ )
    {
        if ( parts[i] == "unicast" || parts[i] == "multicast" )
            distribution = parts[i];
        else if ( startsWith( parts[i], "server_port=" ) )
        {
            method = "RTP/UNICAST";
            StringVector subparts = split( parts[i], "=" );
            StringVector ports = split( subparts[1], "-" );
            remotePorts[0] = strtol( ports[0].c_str(), NULL, 10 );
            remotePorts[1] = strtol( ports[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "interleaved=" ) )
        {
            method = "RTP/RTSP";
            StringVector subparts = split( parts[i], "=" );
            StringVector channels = split( subparts[1], "-" );
            remoteChannels[0] = strtol( channels[0].c_str(), NULL, 10 );
            remoteChannels[1] = strtol( channels[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "port=" ) )
        {
            method = "RTP/MULTICAST";
            StringVector subparts = split( parts[i], "=" );
            StringVector ports = split( subparts[1], "-" );
            localPorts[0] = strtol( ports[0].c_str(), NULL, 10 );
            localPorts[1] = strtol( ports[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "destination=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            localHost = subparts[1];
        }
        else if ( startsWith( parts[i], "ssrc=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            ssrc = strtoll( subparts[1].c_str(), NULL, 16 );
        }
    }

    Debug( 2, "RTSP Method is %s", method.c_str() );
    Debug( 2, "RTSP Distribution is %s", distribution.c_str() );
    Debug( 2, "RTSP SSRC is %lx", ssrc );
    Debug( 2, "RTSP Local Host is %s", localHost.c_str() );
    Debug( 2, "RTSP Local Ports are %d/%d", localPorts[0], localPorts[1] );
    Debug( 2, "RTSP Remote Ports are %d/%d", remotePorts[0], remotePorts[1] );
    Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] );

    message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n";
    if ( !sendCommand( message ) )
        return( -1 );
    if ( !recvResponse( response ) )
        return( -1 );

    lines = split( response, "\r\n" );
    char *rtpInfo = 0;
    for ( size_t i = 0; i < lines.size(); i++ )
    {
        sscanf( lines[i].c_str(), "RTP-Info: %as", &rtpInfo );
    }

    if ( !rtpInfo )
        Fatal( "Unable to get RTP Info identifier from response '%s'", response.c_str() );

    Debug( 2, "Got RTP Info %s", rtpInfo );

    int seq = 0;
    unsigned long rtpTime = 0;
    parts = split( rtpInfo, ";" );
    for ( size_t i = 0; i < parts.size(); i++ )
    {
        if ( startsWith( parts[i], "seq=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            seq = strtol( subparts[1].c_str(), NULL, 10 );
        }
        else if ( startsWith( parts[i], "rtptime=" ) )
        {
            StringVector subparts = split( parts[i], "=" );
            rtpTime = strtol( subparts[1].c_str(), NULL, 10 );
        }
    }

    Debug( 2, "RTSP Seq is %d", seq );
    Debug( 2, "RTSP Rtptime is %ld", rtpTime );

    switch( mMethod )
    {
        case RTP_UNICAST :
        {
            RtpSource *source = new RtpSource( mId, "", localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            rtpDataThread.start();
            rtpCtrlThread.start();

            while( !mStop )
            {
                usleep( 100000 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif

            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );

            rtpDataThread.stop();
            rtpCtrlThread.stop();

            //rtpDataThread.kill( SIGTERM );
            //rtpCtrlThread.kill( SIGTERM );

            rtpDataThread.join();
            rtpCtrlThread.join();
         
            delete mSources[ssrc];
            mSources.clear();

            releasePorts( localPorts[0] );

            break;
        }
        case RTP_RTSP :
        case RTP_RTSP_HTTP :
        {
            RtpSource *source = new RtpSource( mId, "", remoteChannels[0], mHost, remoteChannels[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            // These never actually run
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            Select select( double(config.http_timeout)/1000.0 );
            select.addReader( &mRtspSocket );

            Buffer buffer( ZM_NETWORK_BUFSIZ );
            time_t lastKeepalive = time(NULL);
            std::string keepaliveMessage = "OPTIONS * RTSP/1.0\r\n";
            std::string keepaliveResponse = "RTSP/1.0 200 OK\r\n";
            while ( !mStop && select.wait() >= 0 )
            {
                Select::CommsList readable = select.getReadable();
                if ( readable.size() == 0 )
                {
                    Error( "RTSP timed out" );
                    break;
                }

                static char tempBuffer[ZM_NETWORK_BUFSIZ];
                ssize_t nBytes = mRtspSocket.recv( tempBuffer, sizeof(tempBuffer) );
                buffer.append( tempBuffer, nBytes );
                Debug( 4, "Read %zd bytes on sd %d, %d total", nBytes, mRtspSocket.getReadDesc(), buffer.size() );

                while( buffer.size() > 0 )
                {
                    if ( buffer[0] == '$' )
                    {
                	if ( buffer.size() < 4 )
                	    break;
                        unsigned char channel = buffer[1];
                        unsigned short len = ntohs( *((unsigned short *)(buffer+2)) );

                        Debug( 4, "Got %d bytes left, expecting %d byte packet on channel %d", buffer.size(), len, channel );
                        if ( (unsigned short)buffer.size() < (len+4) )
                        {
                            Debug( 4, "Missing %d bytes, rereading", (len+4)-buffer.size() );
                            break;
                        }
                        if ( channel == remoteChannels[0] )
                        {
                            Debug( 4, "Got %d bytes on data channel %d, packet length is %d", buffer.size(), channel, len );
                            Hexdump( 4, (char *)buffer, 16 );
                            rtpDataThread.recvPacket( buffer+4, len );
                            Debug( 4, "Received" );
                        }
                        else if ( channel == remoteChannels[1] )
                        {
//                            len = ntohs( *((unsigned short *)(buffer+2)) );
//                            Debug( 4, "Got %d bytes on control channel %d", nBytes, channel );
                            Debug( 4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len );
                            Hexdump( 4, (char *)buffer, 16 );
                            rtpCtrlThread.recvPackets( buffer+4, len );
                        }
                        else
                        {
                            Error( "Unexpected channel selector %d in RTSP interleaved data", buffer[1] );
                            buffer.clear();
                            break;
                        }
                        buffer.consume( len+4 );
                        nBytes -= len+4;
                    }
                    else
                    {
                        if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 )
                        {
                            Debug( 4, "Got keepalive response '%s'", (char *)buffer );
                            //buffer.consume( keepaliveResponse.size() );
                            if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
                            {
                                int discardBytes = charPtr-(char *)buffer;
                                buffer -= discardBytes;
                            }
                            else
                            {
                                buffer.clear();
                            }
                        }
                        else
                        {
                            if ( char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) )
                            {
                                int discardBytes = charPtr-(char *)buffer;
                                Warning( "Unexpected format RTSP interleaved data, resyncing by %d bytes", discardBytes );
                                Hexdump( -1, (char *)buffer, discardBytes );
                                buffer -= discardBytes;
                            }
                            else
                            {
                                Warning( "Unexpected format RTSP interleaved data, dumping %d bytes", buffer.size() );
                                Hexdump( -1, (char *)buffer, 32 );
                                buffer.clear();
                            }
                        }
                    }
                }
                if ( (timeout > 0) && ((time(NULL)-lastKeepalive) > (timeout-5)) )
                {
                    if ( !sendCommand( message ) )
                        return( -1 );
                    lastKeepalive = time(NULL);
                }
                buffer.tidy( 1 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif
            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );

            delete mSources[ssrc];
            mSources.clear();

            break;
        }
        case RTP_MULTICAST :
        {
            RtpSource *source = new RtpSource( mId, localHost, localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
            mSources[ssrc] = source;
            RtpDataThread rtpDataThread( *this, *source );
            RtpCtrlThread rtpCtrlThread( *this, *source );

            rtpDataThread.start();
            rtpCtrlThread.start();

            while( !mStop )
            {
                usleep( 100000 );
            }
#if 0
            message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );
#endif
            message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
            if ( !sendCommand( message ) )
                return( -1 );
            if ( !recvResponse( response ) )
                return( -1 );

            rtpDataThread.stop();
            rtpCtrlThread.stop();

            rtpDataThread.join();
            rtpCtrlThread.join();
         
            delete mSources[ssrc];
            mSources.clear();

            releasePorts( localPorts[0] );
            break;
        }
        default:
        {
            Panic( "Got unexpected method %d", mMethod );
            break;
        }
    }

    return( 0 );
}