RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, const std::string &remoteHost, int remotePortBase, uint32_t ssrc, uint16_t seq, uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId ) : mId( id ), mSsrc( ssrc ), mLocalHost( localHost ), mRemoteHost( remoteHost ), mRtpClock( rtpClock ), mCodecId( codecId ), mFrame( 65536 ), mFrameCount( 0 ), mFrameGood( true ), mFrameReady( false ), mFrameProcessed( false ) { char hostname[256] = ""; gethostname( hostname, sizeof(hostname) ); mCname = stringtf( "zm-%d@%s", mId, hostname ); Debug( 3, "RTP CName = %s", mCname.c_str() ); init( seq ); mMaxSeq = seq - 1; mProbation = MIN_SEQUENTIAL; mLocalPortChans[0] = localPortBase; mLocalPortChans[1] = localPortBase+1; mRemotePortChans[0] = remotePortBase; mRemotePortChans[1] = remotePortBase+1; mRtpFactor = mRtpClock; mBaseTimeReal = tvNow(); mBaseTimeNtp = tvZero(); mBaseTimeRtp = rtpTime; mLastSrTimeReal = tvZero(); mLastSrTimeNtp = tvZero(); mLastSrTimeRtp = 0; if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4) Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." ); }
RtspThread::RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth, bool rtsp_describe) : mId( id ), mMethod( method ), mProtocol( protocol ), mHost( host ), mPort( port ), mPath( path ), mRtspDescribe( rtsp_describe ), mSessDesc( 0 ), mFormatContext( 0 ), mSeq( 0 ), mSession( 0 ), mSsrc( 0 ), mDist( UNDEFINED ), mRtpTime( 0 ), mStop( false ) { mUrl = mProtocol+"://"+mHost+":"+mPort; if ( !mPath.empty() ) { if ( mPath[0] == '/' ) mUrl += mPath; else mUrl += '/'+mPath; } mSsrc = rand(); Debug( 2, "RTSP Local SSRC is %x", mSsrc ); if ( mMethod == RTP_RTSP_HTTP ) mHttpSession = stringtf( "%d", rand() ); mNeedAuth = false; StringVector parts = split(auth,":"); if (parts.size() > 1) mAuthenticator = new Authenticator(parts[0], parts[1]); else mAuthenticator = new Authenticator(parts[0], ""); }
/** * @brief * * @return */ int LocalFileDump::run() { std::string filePath; FILE *fileDesc = NULL; if ( waitForProviders() ) { while( !mStop ) { mQueueMutex.lock(); if ( !mFrameQueue.empty() ) { for ( FrameQueue::iterator iter = mFrameQueue.begin(); iter != mFrameQueue.end(); iter++ ) { const FeedFrame *frame = iter->get(); Info( "F:%ld", frame->buffer().size() ); if ( filePath.empty() ) { filePath = stringtf( "%s/%s-%s", mLocation.c_str(), mName.c_str(), frame->provider()->cidentity() ); Info( "Path: %s", filePath.c_str() ); fileDesc = fopen( filePath.c_str(), "w" ); if ( !fileDesc ) Fatal( "Failed to open dump file '%s': %s", filePath.c_str(), strerror(errno) ); } if ( fwrite( frame->buffer().data(), frame->buffer().size(), 1, fileDesc ) <= 0 ) Fatal( "Failed to write to dump file '%s': %s", filePath.c_str(), strerror(errno) ); //delete *iter; } mFrameQueue.clear(); } mQueueMutex.unlock(); checkProviders(); usleep( INTERFRAME_TIMEOUT ); } fclose( fileDesc ); } cleanup(); return( 0 ); }
void Mutex::unlock() { if ( pthread_mutex_unlock( &mMutex ) < 0 ) throw ThreadException( stringtf( "Unable to unlock pthread mutex: %s", strerror(errno) ) ); }
void Mutex::lock( double secs ) { struct timespec timeout = getTimeout( secs ); if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); }
Mutex::Mutex() { if ( pthread_mutex_init( &mMutex, NULL ) < 0 ) throw ThreadException( stringtf( "Unable to create pthread mutex: %s", strerror(errno) ) ); }
/** * @brief * * @param request * * @return */ bool RtspConnection::handleRequest( const std::string &request ) { Debug( 2, "Handling RTSP request: %s (%zd bytes)", request.c_str(), request.size() ); StringTokenList lines( request, "\r\n" ); if ( lines.size() <= 0 ) { Error( "Unable to split request '%s' into tokens", request.c_str() ); return( false ); } StringTokenList parts( lines[0], " " ); if ( parts.size() != 3 ) { Error( "Unable to split request part '%s' into tokens", lines[0].c_str() ); return( false ); } std::string requestType = parts[0]; Debug( 4, "Got request '%s'", requestType.c_str() ); std::string requestUrl = parts[1]; Debug( 4, "Got requestUrl '%s'", requestUrl.c_str() ); std::string requestVer = parts[2]; Debug( 4, "Got requestVer '%s'", requestVer.c_str() ); if ( requestVer != "RTSP/1.0" ) { Error( "Unexpected RTSP version '%s'", requestVer.c_str() ); return( false ); } // Extract headers from request Headers requestHeaders; for ( int i = 1; i < lines.size(); i++ ) { StringTokenList parts( lines[i], ": " ); if ( parts.size() != 2 ) { Error( "Unable to split request header '%s' into tokens", lines[i].c_str() ); return( false ); } Debug( 4, "Got header '%s', value '%s'", parts[0].c_str(), parts[1].c_str() ); requestHeaders.insert( Headers::value_type( parts[0], parts[1] ) ); } if ( requestHeaders.find("CSeq") == requestHeaders.end() ) { Error( "No CSeq header found" ); return( false ); } Debug( 4, "Got sequence number %s", requestHeaders["CSeq"].c_str() ); uint32_t session = 0; if ( requestHeaders.find("Session") != requestHeaders.end() ) { Debug( 4, "Got session header, '%s', passing to session", requestHeaders["Session"].c_str() ); session = strtol( requestHeaders["Session"].c_str(), NULL, 16 ); } Headers responseHeaders; responseHeaders.insert( Headers::value_type( "CSeq", requestHeaders["CSeq"] ) ); if ( requestType == "OPTIONS" ) { responseHeaders.insert( Headers::value_type( "Public", "DESCRIBE, SETUP, PLAY, GET_PARAMETER, TEARDOWN" ) ); return( sendResponse( responseHeaders ) ); } else if ( requestType == "DESCRIBE" ) { FeedProvider *provider = validateRequestUrl( requestUrl ); if ( !provider ) { sendResponse( responseHeaders, "", 404, "Not Found" ); return( false ); } const VideoProvider *videoProvider = dynamic_cast<const VideoProvider *>(provider); int codec = AV_CODEC_ID_MPEG4; int width = videoProvider->width(); int height = videoProvider->height(); FrameRate frameRate = 15; //FrameRate frameRate = videoProvider->frameRate(); int bitRate = 90000; int quality = 70; std::string sdpFormatString = "v=0\r\n" "o=- %jd %jd IN IP4 %s\r\n" "s=ZoneMinder Stream\r\n" "i=Media Streamers\r\n" "c=IN IP4 0.0.0.0\r\n" "t=0 0\r\n" "a=control:*\r\n" "a=range:npt=0.000000-\r\n"; uint64_t now64 = time64(); char hostname[HOST_NAME_MAX] = ""; if ( gethostname( hostname, sizeof(hostname) ) < 0 ) Fatal( "Can't gethostname: %s", strerror(errno) ); std::string sdpString = stringtf( sdpFormatString, now64, now64, hostname ); if ( codec == AV_CODEC_ID_H264 ) { if ( provider->cl4ss() == "RawH264Input" ) { std::string encoderKey = H264Relay::getPoolKey( provider->identity(), width, height, frameRate, bitRate, quality ); if ( !(mEncoder = Encoder::getPooledEncoder( encoderKey )) ) { H264Relay *h264Relay = NULL; mEncoder = h264Relay = new H264Relay( provider->identity(), width, height, frameRate, bitRate, quality ); mEncoder->registerProvider( *provider ); Encoder::poolEncoder( mEncoder ); h264Relay->start(); } sdpString += mEncoder->sdpString( 1 ); // XXX - Should be variable responseHeaders.insert( Headers::value_type( "Content-length", stringtf( "%zd", sdpString.length() ) ) ); } else { std::string encoderKey = H264Encoder::getPoolKey( provider->identity(), width, height, frameRate, bitRate, quality ); if ( !(mEncoder = Encoder::getPooledEncoder( encoderKey )) ) { H264Encoder *h264Encoder = NULL; mEncoder = h264Encoder = new H264Encoder( provider->identity(), width, height, frameRate, bitRate, quality ); mEncoder->registerProvider( *provider ); Encoder::poolEncoder( mEncoder ); h264Encoder->start(); } sdpString += mEncoder->sdpString( 1 ); // XXX - Should be variable responseHeaders.insert( Headers::value_type( "Content-length", stringtf( "%zd", sdpString.length() ) ) ); } } else if ( codec == AV_CODEC_ID_MPEG4 ) { std::string encoderKey = MpegEncoder::getPoolKey( provider->identity(), width, height, frameRate, bitRate, quality ); if ( !(mEncoder = Encoder::getPooledEncoder( encoderKey )) ) { MpegEncoder *mpegEncoder = NULL; mEncoder = mpegEncoder = new MpegEncoder( provider->identity(), width, height, frameRate, bitRate, quality ); mEncoder->registerProvider( *provider ); Encoder::poolEncoder( mEncoder ); mpegEncoder->start(); } sdpString += mEncoder->sdpString( 1 ); // XXX - Should be variable responseHeaders.insert( Headers::value_type( "Content-length", stringtf( "%zd", sdpString.length() ) ) ); } return( sendResponse( responseHeaders, sdpString ) ); } else if ( requestType == "SETUP" ) { // These commands are handled by RTSP session so pass them on and send any required responses RtspSession *rtspSession = 0; if ( session ) { rtspSession = mRtspController->getSession( session ); } else { rtspSession = mRtspController->newSession( this, mEncoder ); } if ( rtspSession->recvRequest( requestType, requestUrl, requestHeaders, responseHeaders ) ) return( sendResponse( responseHeaders ) ); return( false ); } else if ( requestType == "PLAY" || requestType == "GET_PARAMETER" || requestType == "TEARDOWN" ) { // These commands are handled by RTSP session so pass them on and send any required responses RtspSession *rtspSession = 0; if ( session ) { rtspSession = mRtspController->getSession( session ); if ( rtspSession && rtspSession->recvRequest( requestType, requestUrl, requestHeaders, responseHeaders ) ) return( sendResponse( responseHeaders ) ); } return( sendResponse( responseHeaders, "", 454, "Session Not Found" ) ); } Error( "Unrecognised RTSP command '%s'", requestType.c_str() ); return( sendResponse( responseHeaders, "", 405, "Method not implemented" ) ); }
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 ); }
void zmLoadConfig() { FILE *cfg; char line[512]; if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL ) { Fatal( "Can't open %s: %s", ZM_CONFIG, strerror(errno) ); } while ( fgets( line, sizeof(line), cfg ) != NULL ) { char *line_ptr = line; // Trim off any cr/lf line endings int chomp_len = strcspn( line_ptr, "\r\n" ); line_ptr[chomp_len] = '\0'; // Remove leading white space int white_len = strspn( line_ptr, " \t" ); line_ptr += white_len; // Check for comment or empty line if ( *line_ptr == '\0' || *line_ptr == '#' ) continue; // Remove trailing white space char *temp_ptr = line_ptr+strlen(line_ptr)-1; while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) { *temp_ptr-- = '\0'; temp_ptr--; } // Now look for the '=' in the middle of the line temp_ptr = strchr( line_ptr, '=' ); if ( !temp_ptr ) { Warning( "Invalid data in %s: '%s'", ZM_CONFIG, line ); continue; } // Assign the name and value parts char *name_ptr = line_ptr; char *val_ptr = temp_ptr+1; // Trim trailing space from the name part do { *temp_ptr = '\0'; temp_ptr--; } while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); // Remove leading white space from the value part white_len = strspn( val_ptr, " \t" ); val_ptr += white_len; if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) staticConfig.DB_HOST = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 ) staticConfig.DB_NAME = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 ) staticConfig.DB_USER = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) staticConfig.DB_PASS = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) staticConfig.PATH_WEB = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 ) staticConfig.SERVER_NAME = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_SERVER_NAME" ) == 0 ) staticConfig.SERVER_NAME = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 ) staticConfig.SERVER_ID = atoi(val_ptr); else { // We ignore this now as there may be more parameters than the // c/c++ binaries are bothered about // Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG ); } } // end foreach line of the config fclose( cfg ); zmDbConnect(); config.Load(); config.Assign(); // Populate the server config entries if ( ! staticConfig.SERVER_ID ) { if ( ! staticConfig.SERVER_NAME.empty() ) { Debug( 1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str() ); std::string sql = stringtf("SELECT Id FROM Servers WHERE Name='%s'", staticConfig.SERVER_NAME.c_str() ); if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str() ); } } // end if has SERVER_NAME } else if ( staticConfig.SERVER_NAME.empty() ) { Debug( 1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID ); std::string sql = stringtf("SELECT Name FROM Servers WHERE Id='%d'", staticConfig.SERVER_ID ); if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID ); } } if ( ! staticConfig.SERVER_ID ) { Debug( 1, "No Server ID or Name specified in config. Not using Multi-Server Mode." ); } else { Debug( 1, "Server is %d: using Multi-Server Mode.", staticConfig.SERVER_ID ); } }
/** * @brief * * @param name * @param width * @param height * @param frameRate * @param bitRate * @param quality * * @return */ std::string H264Encoder::getPoolKey( const std::string &name, uint16_t width, uint16_t height, FrameRate frameRate, uint32_t bitRate, uint8_t quality ) { return( stringtf( "%s-h264-%dx%d@%d/%d-%d(%d)", name.c_str(), width, height, frameRate.num, frameRate.den, bitRate, quality ) ); }
Condition::Condition( Mutex &mutex ) : mMutex( mutex ) { if ( pthread_cond_init( &mCondition, NULL ) < 0 ) throw ThreadException( stringtf( "Unable to create pthread condition: %s", strerror(errno) ) ); }
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 ); }
/** * @brief * * @param outputFormat * * @return */ AVFormatContext *Mp4FileOutput::openFile( AVOutputFormat *outputFormat ) { /* allocate the output media context */ AVFormatContext *outputContext = avformat_alloc_context(); if ( !outputContext ) Fatal( "Unable to allocate output context" ); outputContext->oformat = outputFormat; AVDictionary *opts = NULL; //av_dict_set( &opts, "width", "352", 0 ); //av_dict_set( &opts, "height", "288", 0 ); //av_dict_set( &opts, "vpre", "medium", 0 ); av_dict_set( &outputContext->metadata, "author", "Pontis EMC Systems", 0 ); av_dict_set( &outputContext->metadata, "comment", "PECOS Footage", 0 ); //av_dict_set( &outputContext->metadata, "copyright", "COPYRIGHT", 0 ); av_dict_set( &outputContext->metadata, "title", "TITLE", 0 ); // x264 Baseline //avSetH264Preset( &opts, "medium" ); //av_dict_set( &opts, "b", "200k", 0 ); //av_dict_set( &opts, "bt", "240k", 0 ); avDumpDict( opts ); /* add the audio and video streams using the default format codecs and initialize the codecs */ AVStream *videoStream = NULL; AVCodecContext *videoCodecContext = NULL; videoStream = avformat_new_stream( outputContext, NULL ); if ( !videoStream ) Fatal( "Could not alloc video stream" ); videoCodecContext = videoStream->codec; videoCodecContext->codec_id = outputFormat->video_codec; videoCodecContext->codec_type = AVMEDIA_TYPE_VIDEO; videoCodecContext->width = mVideoParms.width(); videoCodecContext->height = mVideoParms.height(); videoCodecContext->time_base = mVideoParms.frameRate(); //videoCodecContext->time_base.num = 1; //videoCodecContext->time_base.den = 90000; //videoCodecContext->pix_fmt = mVideoParms.pixelFormat(); // some formats want stream headers to be separate if ( outputContext->oformat->flags & AVFMT_GLOBALHEADER ) videoCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER; //av_dump_format( outputContext, 0, filename, 1 ); time_t now = time( NULL ); std::string filename = stringtf( "%s/%s-%ld.%s", mLocation.c_str(), cname(), now, mExtension.c_str() ); snprintf( outputContext->filename, sizeof(outputContext->filename), "%s", filename.c_str() ); Info( "Writing to movie file '%s'", outputContext->filename ); if ( avio_open( &outputContext->pb, filename.c_str(), AVIO_FLAG_WRITE ) < 0 ) Fatal( "Could not open output filename '%s'", filename.c_str() ); /* write the stream header, if any */ avformat_write_header( outputContext, &opts ); avDumpDict( opts ); return( outputContext ); }
/** * @brief * * @return */ int Mp4FileOutput::run() { //const int MAX_EVENT_HEAD_AGE = 2; ///< Number of seconds of video before event to save const int MAX_EVENT_TAIL_AGE = 3; ///< Number of seconds of video after event to save typedef enum { IDLE, PREALARM, ALARM, ALERT } AlarmState; if ( waitForProviders() ) { /* auto detect the output format from the name. default is mpeg. */ AVOutputFormat *outputFormat = av_guess_format( mExtension.c_str(), NULL, NULL ); if ( !outputFormat ) Fatal( "Could not deduce output format from '%s'", mExtension.c_str() ); //AVFormatContext *outputContext = openFile( outputFormat ); AVFormatContext *outputContext = NULL; double videoTimeOffset = 0.0L; uint64_t videoFrameCount = 0; AlarmState alarmState = IDLE; uint64_t alarmTime = 0; int eventCount = 0; while( !mStop ) { while( !mStop ) { mQueueMutex.lock(); if ( !mFrameQueue.empty() ) { for ( FrameQueue::iterator iter = mFrameQueue.begin(); iter != mFrameQueue.end(); iter++ ) { const FeedFrame *frame = iter->get(); Debug( 3, "Frame type %d", frame->mediaType() ); if ( frame->mediaType() == FeedFrame::FRAME_TYPE_VIDEO ) { // This is an alarm detection frame const MotionFrame *motionFrame = dynamic_cast<const MotionFrame *>(frame); //const VideoProvider *provider = dynamic_cast<const VideoProvider *>(frame->provider()); AlarmState lastAlarmState = alarmState; uint64_t now = time64(); Debug( 3, "Motion frame, alarmed %d", motionFrame->alarmed() ); if ( motionFrame->alarmed() ) { alarmState = ALARM; alarmTime = now; if ( lastAlarmState == IDLE ) { // Create new event eventCount++; std::string path = stringtf( "%s/img-%s-%d-%ju.jpg", mLocation.c_str(), mName.c_str(), eventCount, motionFrame->id() ); //Info( "PF:%d @ %dx%d", motionFrame->pixelFormat(), motionFrame->width(), motionFrame->height() ); Image image( motionFrame->pixelFormat(), motionFrame->width(), motionFrame->height(), motionFrame->buffer().data() ); image.writeJpeg( path.c_str() ); } } else if ( lastAlarmState == ALARM ) { alarmState = ALERT; } else if ( lastAlarmState == ALERT ) { Debug( 3, "Frame age %.2lf", frame->age( alarmTime ) ); if ( (0.0l-frame->age( alarmTime )) > MAX_EVENT_TAIL_AGE ) alarmState = IDLE; } else { alarmState = IDLE; } Debug( 3, "Alarm state %d (%d)", alarmState, lastAlarmState ); } else { bool keyFrame = false; const uint8_t *startPos = h264StartCode( frame->buffer().head(), frame->buffer().tail() ); while ( startPos < frame->buffer().tail() ) { while( !*(startPos++) ) ; const uint8_t *nextStartPos = h264StartCode( startPos, frame->buffer().tail() ); int frameSize = nextStartPos-startPos; unsigned char type = startPos[0] & 0x1F; unsigned char nri = startPos[0] & 0x60; Debug( 3, "Frame Type %d, NRI %d (%02x), %d bytes, ts %jd", type, nri>>5, startPos[0], frameSize, frame->timestamp() ); if ( type == NAL_IDR_SLICE ) keyFrame = true; startPos = nextStartPos; } videoTimeOffset += (double)mVideoParms.frameRate().num / mVideoParms.frameRate().den; if ( keyFrame ) { // We can do file opening/closing now if ( alarmState != IDLE && !outputContext ) { outputContext = openFile( outputFormat ); videoTimeOffset = 0.0L; videoFrameCount = 0; } else if ( alarmState == IDLE && outputContext ) { closeFile( outputContext ); outputContext = NULL; } } /*if ( keyFrame && (videoTimeOffset >= mMaxLength) ) { closeFile( outputContext ); outputContext = openFile( outputFormat ); videoTimeOffset = 0.0L; videoFrameCount = 0; }*/ if ( outputContext ) { AVStream *videoStream = outputContext->streams[0]; AVCodecContext *videoCodecContext = videoStream->codec; AVPacket packet; av_init_packet(&packet); packet.flags |= keyFrame ? AV_PKT_FLAG_KEY : 0; packet.stream_index = videoStream->index; packet.data = (uint8_t*)frame->buffer().data(); packet.size = frame->buffer().size(); //packet.pts = packet.dts = AV_NOPTS_VALUE; packet.pts = packet.dts = (videoFrameCount * mVideoParms.frameRate().num * videoCodecContext->time_base.den) / (mVideoParms.frameRate().den * videoCodecContext->time_base.num); Info( "vfc: %ju, vto: %.2lf, kf: %d, pts: %jd", videoFrameCount, videoTimeOffset, keyFrame, packet.pts ); int result = av_interleaved_write_frame(outputContext, &packet); if ( result != 0 ) Fatal( "Error while writing video frame: %d", result ); } videoFrameCount++; } } mFrameQueue.clear(); } mQueueMutex.unlock(); checkProviders(); usleep( INTERFRAME_TIMEOUT ); } } if ( outputContext ) closeFile( outputContext ); } cleanup(); return 0; }
void MonitorStream::runStream() { if ( type == STREAM_SINGLE ) { // Not yet migrated over to stream class SingleImage(scale); return; } openComms(); if ( !checkInitialised() ) { Error("Not initialized"); return; } updateFrameRate(monitor->GetFPS()); if ( type == STREAM_JPEG ) fputs("Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n", stdout); // point to end which is theoretically not a valid value because all indexes are % image_buffer_count unsigned int last_read_index = monitor->image_buffer_count; time_t stream_start_time; time(&stream_start_time); frame_count = 0; temp_image_buffer = 0; temp_image_buffer_count = playback_buffer; temp_read_index = temp_image_buffer_count; temp_write_index = temp_image_buffer_count; std::string swap_path; bool buffered_playback = false; // Last image and timestamp when paused, will be resent occasionally to prevent timeout Image *paused_image = NULL; struct timeval paused_timestamp; // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id const int max_swap_len_suffix = 15; int swap_path_length = staticConfig.PATH_SWAP.length() + 1; // +1 for NULL terminator int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()) + 1; int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey) + 1; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; if ( connkey && ( playback_buffer > 0 ) ) { if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); } else { swap_path = staticConfig.PATH_SWAP; Debug( 3, "Checking swap path folder: %s", swap_path.c_str() ); if ( checkSwapPath(swap_path.c_str(), true) ) { swap_path += stringtf("/zmswap-m%d", monitor->Id()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); if ( checkSwapPath(swap_path.c_str(), true) ) { swap_path += stringtf("/zmswap-q%06d", connkey); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); if ( checkSwapPath(swap_path.c_str(), true) ) { buffered_playback = true; } } } if ( !buffered_playback ) { Error("Unable to validate swap image path, disabling buffered playback"); } else { Debug(2, "Assigning temporary buffer"); temp_image_buffer = new SwapImage[temp_image_buffer_count]; memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count ); Debug( 2, "Assigned temporary buffer" ); } } } else { Debug(2, "Not using playback_buffer"); } // end if connkey & playback_buffer float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) while ( !zm_terminate ) { bool got_command = false; if ( feof(stdout) ) { Debug(2,"feof stdout"); break; } else if ( ferror(stdout) ) { Debug(2,"ferror stdout"); break; } else if ( !monitor->ShmValid() ) { Debug(2,"monitor not valid.... maybe we should wait until it comes back."); break; } gettimeofday(&now, NULL); bool was_paused = paused; if ( connkey ) { while(checkCommandQueue()) { Debug(2, "Have checking command Queue for connkey: %d", connkey ); got_command = true; } // Update modified time of the socket .lock file so that we can tell which ones are stale. if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { touch(sock_path_lock); last_comm_update = now; } } if ( paused ) { if ( !was_paused ) { int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; Debug(1,"Saving paused image from index %d",index); paused_image = new Image( *monitor->image_buffer[index].image ); paused_timestamp = *(monitor->image_buffer[index].timestamp); } } else if ( paused_image ) { Debug(1,"Clearing paused_image"); delete paused_image; paused_image = NULL; } if ( buffered_playback && delayed ) { if ( temp_read_index == temp_write_index ) { // Go back to live viewing Debug( 1, "Exceeded temporary streaming buffer" ); // Clear paused flag paused = false; // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; } else { if ( !paused ) { int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; if ( !swap_image->valid ) { paused = true; delayed = true; temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); } else { //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due if ( actual_delta_time > expected_delta_time ) { //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); if ( temp_index%frame_mod == 0 ) { Debug( 2, "Sending delayed frame %d", temp_index ); // Send the next frame if ( ! sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) zm_terminate = true; memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); //frame_sent = true; } temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count); } } } else if ( step != 0 ) { temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; // Send the next frame if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) ) zm_terminate = true; memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); //frame_sent = true; step = 0; } else { //paused? int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; if ( got_command || actual_delta_time > 5 ) { // Send keepalive Debug( 2, "Sending keepalive frame %d", temp_index ); // Send the next frame if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) zm_terminate = true; //frame_sent = true; } } // end if (!paused) or step or paused } // end if have exceeded buffer or not if ( temp_read_index == temp_write_index ) { // Go back to live viewing Warning( "Rewound over write index, resuming live play" ); // Clear paused flag paused = false; // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; } } // end if ( buffered_playback && delayed ) if ( last_read_index != monitor->shared_data->last_write_index ) { // have a new image to send int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary last_read_index = monitor->shared_data->last_write_index; Debug( 2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", index, frame_mod, frame_count, paused, delayed ); if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ( !paused && !delayed ) { // Send the next frame Monitor::Snapshot *snap = &monitor->image_buffer[index]; Debug(2, "sending Frame."); if ( !sendFrame(snap->image, snap->timestamp) ) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; } // Perhaps we should use NOW instead. memcpy(&last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp)); //frame_sent = true; temp_read_index = temp_write_index; } else { double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; if ( actual_delta_time > 5 ) { if ( paused_image ) { // Send keepalive Debug(2, "Sending keepalive frame "); // Send the next frame if ( !sendFrame(paused_image, &paused_timestamp) ) zm_terminate = true; } else { Debug(2, "Would have sent keepalive frame, but had no paused_image "); } } } } // end if should send frame if ( buffered_playback && !paused ) { if ( monitor->shared_data->valid ) { if ( monitor->image_buffer[index].timestamp->tv_sec ) { int temp_index = temp_write_index%temp_image_buffer_count; Debug(2, "Storing frame %d", temp_index); if ( !temp_image_buffer[temp_index].valid ) { snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path.c_str(), temp_index ); temp_image_buffer[temp_index].valid = true; } memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); if ( temp_write_index == temp_read_index ) { // Go back to live viewing Warning( "Exceeded temporary buffer, resuming live play" ); paused = false; delayed = false; replay_rate = ZM_RATE_BASE; } } else { Warning( "Unable to store frame as timestamp invalid" ); } } else { Warning( "Unable to store frame as shared memory invalid" ); } } // end if buffered playback frame_count++; } else { Debug(4,"Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); Debug(4, "Sleeping for (%d)", sleep_time); usleep(sleep_time); if ( ttl ) { if ( (now.tv_sec - stream_start_time) > ttl ) { Debug(2, "now(%d) - start(%d) > ttl(%d) break", now.tv_sec, stream_start_time, ttl); break; } } if ( ! last_frame_sent ) { // If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value. last_frame_sent = now.tv_sec; Warning( "no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d) ", frame_mod, frame_count ); } else if ( (!paused) && ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) ) { Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); break; } } // end while if ( buffered_playback ) { Debug(1, "Cleaning swap files from %s", swap_path.c_str()); struct stat stat_buf; if ( stat(swap_path.c_str(), &stat_buf) < 0 ) { if ( errno != ENOENT ) { Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno)); } } else if ( !S_ISDIR(stat_buf.st_mode) ) { Error("Swap image path '%s' is not a directory", swap_path.c_str()); } else { char glob_pattern[PATH_MAX] = ""; snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str()); glob_t pglob; int glob_status = glob(glob_pattern, 0, 0, &pglob); if ( glob_status != 0 ) { if ( glob_status < 0 ) { Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); } else { Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); } } else { for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { if ( unlink(pglob.gl_pathv[i]) < 0 ) { Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno)); } } } globfree( &pglob ); if ( rmdir(swap_path.c_str()) < 0 ) { Error( "Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno) ); } } // end if checking for swap_path } // end if buffered_playback closeComms(); } // end MonitorStream::runStream
Condition::~Condition() { if ( pthread_cond_destroy( &mCondition ) < 0 ) throw ThreadException( stringtf( "Unable to destroy pthread condition: %s", strerror(errno) ) ); }
void Condition::wait() { // Locking done outside of this function if ( pthread_cond_wait( &mCondition, mMutex.getMutex() ) < 0 ) throw ThreadException( stringtf( "Unable to wait pthread condition: %s", strerror(errno) ) ); }
int RemoteCameraHttp::GetResponse() { int buffer_len; #if HAVE_LIBPCRE if ( method == REGEXP ) { const char *header = 0; int header_len = 0; const char *http_version = 0; int status_code = 0; const char *status_mesg = 0; const char *connection_type = ""; int content_length = 0; const char *content_type = ""; const char *content_boundary = ""; const char *subheader = 0; int subheader_len = 0; //int subcontent_length = 0; //const char *subcontent_type = ""; while ( true ) { switch( state ) { case HEADER : { static RegExpr *header_expr = 0; static RegExpr *status_expr = 0; static RegExpr *connection_expr = 0; static RegExpr *content_length_expr = 0; static RegExpr *content_type_expr = 0; while ( ! ( buffer_len = ReadData( buffer ) ) ) { } if ( buffer_len < 0 ) { Error( "Unable to read header data" ); return( -1 ); } if ( !header_expr ) header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) { header = header_expr->MatchString( 1 ); header_len = header_expr->MatchLength( 1 ); Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); if ( !status_expr ) status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); if ( status_expr->Match( header, header_len ) < 4 ) { Error( "Unable to extract HTTP status from header" ); return( -1 ); } http_version = status_expr->MatchString( 1 ); status_code = atoi( status_expr->MatchString( 2 ) ); status_mesg = status_expr->MatchString( 3 ); if ( status_code == 401 ) { if ( mNeedAuth ) { Error( "Failed authentication: " ); return( -1 ); } mNeedAuth = true; std::string Header = header; mAuthenticator->checkAuthResponse(Header); if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { Debug( 2, "Need Digest Authentication" ); request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); if ( strcmp( config.http_version, "1.0" ) == 0 ) request += stringtf( "Connection: Keep-Alive\r\n" ); request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); request += "\r\n"; Debug( 2, "New request header: %s", request.c_str() ); return( 0 ); } } else if ( status_code < 200 || status_code > 299 ) { Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); return( -1 ); } Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); if ( !connection_expr ) connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); if ( connection_expr->Match( header, header_len ) == 2 ) { connection_type = connection_expr->MatchString( 1 ); Debug( 3, "Got connection '%s'", connection_type ); } if ( !content_length_expr ) content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); if ( content_length_expr->Match( header, header_len ) == 2 ) { content_length = atoi( content_length_expr->MatchString( 1 ) ); Debug( 3, "Got content length '%d'", content_length ); } if ( !content_type_expr ) content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); if ( content_type_expr->Match( header, header_len ) >= 2 ) { content_type = content_type_expr->MatchString( 1 ); Debug( 3, "Got content type '%s'\n", content_type ); if ( content_type_expr->MatchCount() > 2 ) { content_boundary = content_type_expr->MatchString( 2 ); Debug( 3, "Got content boundary '%s'", content_boundary ); } } if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { // Single image mode = SINGLE_IMAGE; format = JPEG; state = CONTENT; } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { // Single image mode = SINGLE_IMAGE; format = X_RGB; state = CONTENT; } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { // Single image mode = SINGLE_IMAGE; format = X_RGBZ; state = CONTENT; } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { // Image stream, so start processing if ( !content_boundary[0] ) { Error( "No content boundary found in header '%s'", header ); return( -1 ); } mode = MULTI_IMAGE; state = SUBHEADER; } //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) //{ //// MPEG stream, coming soon! //} else { Error( "Unrecognised content type '%s'", content_type ); return( -1 ); } buffer.consume( header_len ); } else { Debug( 3, "Unable to extract header from stream, retrying" ); //return( -1 ); } break; } case SUBHEADER : { static RegExpr *subheader_expr = 0; static RegExpr *subcontent_length_expr = 0; static RegExpr *subcontent_type_expr = 0; if ( !subheader_expr ) { char subheader_pattern[256] = ""; snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); } if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { subheader = subheader_expr->MatchString( 1 ); subheader_len = subheader_expr->MatchLength( 1 ); Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); if ( !subcontent_length_expr ) subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) { content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); Debug( 3, "Got subcontent length '%d'", content_length ); } if ( !subcontent_type_expr ) subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) { content_type = subcontent_type_expr->MatchString( 1 ); Debug( 3, "Got subcontent type '%s'", content_type ); } buffer.consume( subheader_len ); state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); while ( ! ( buffer_len = ReadData( buffer ) ) ) { } if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); } } break; } case CONTENT : { // if content_type is something like image/jpeg;size=, this will strip the ;size= char * semicolon = strchr( (char *)content_type, ';' ); if ( semicolon ) { *semicolon = '\0'; } if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { format = JPEG; } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { format = X_RGB; } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { format = X_RGBZ; } else { Error( "Found unsupported content type '%s'", content_type ); return( -1 ); } if ( content_length ) { while ( (long)buffer.size() < content_length ) { Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); if ( ReadData( buffer ) < 0 ) { Error( "Unable to read content" ); return( -1 ); } } Debug( 3, "Got end of image by length, content-length = %d", content_length ); } else { while ( !content_length ) { while ( ! ( buffer_len = ReadData( buffer ) ) ) { } if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } static RegExpr *content_expr = 0; if ( mode == MULTI_IMAGE ) { if ( !content_expr ) { char content_pattern[256] = ""; snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); } if ( content_expr->Match( buffer, buffer.size() ) == 2 ) { content_length = content_expr->MatchLength( 1 ); Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); } } } } if ( mode == SINGLE_IMAGE ) { state = HEADER; Disconnect(); } else { state = SUBHEADER; } Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); return( content_length ); } case HEADERCONT : case SUBHEADERCONT : { // Ignore break; } } } } else #endif // HAVE_LIBPCRE { if ( method == REGEXP ) { Warning( "Unable to use netcam regexps as not compiled with libpcre" ); } static const char *http_match = "HTTP/"; static const char *connection_match = "Connection:"; static const char *content_length_match = "Content-length:"; static const char *content_type_match = "Content-type:"; static const char *boundary_match = "boundary="; static const char *authenticate_match = "WWW-Authenticate:"; static int http_match_len = 0; static int connection_match_len = 0; static int content_length_match_len = 0; static int content_type_match_len = 0; static int boundary_match_len = 0; static int authenticate_match_len = 0; if ( !http_match_len ) http_match_len = strlen( http_match ); if ( !connection_match_len ) connection_match_len = strlen( connection_match ); if ( !content_length_match_len ) content_length_match_len = strlen( content_length_match ); if ( !content_type_match_len ) content_type_match_len = strlen( content_type_match ); if ( !boundary_match_len ) boundary_match_len = strlen( boundary_match ); if ( !authenticate_match_len ) authenticate_match_len = strlen( authenticate_match ); static int n_headers; //static char *headers[32]; static int n_subheaders; //static char *subheaders[32]; static char *http_header; static char *connection_header; static char *content_length_header; static char *content_type_header; static char *boundary_header; static char *authenticate_header; static char subcontent_length_header[32]; static char subcontent_type_header[64]; static char http_version[16]; static char status_code[16]; static char status_mesg[256]; static char connection_type[32]; static int content_length; static char content_type[32]; static char content_boundary[64]; static int content_boundary_len; while ( true ) { switch( state ) { case HEADER : { n_headers = 0; http_header = 0; connection_header = 0; content_length_header = 0; content_type_header = 0; authenticate_header = 0; http_version[0] = '\0'; status_code [0]= '\0'; status_mesg [0]= '\0'; connection_type [0]= '\0'; content_length = 0; content_type[0] = '\0'; content_boundary[0] = '\0'; content_boundary_len = 0; } case HEADERCONT : { while ( ! ( buffer_len = ReadData( buffer ) ) ) { } if ( buffer_len < 0 ) { Error( "Unable to read header" ); return( -1 ); } char *crlf = 0; char *header_ptr = (char *)buffer; int header_len = buffer.size(); bool all_headers = false; while( true ) { int crlf_len = memspn( header_ptr, "\r\n", header_len ); if ( n_headers ) { if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { *header_ptr = '\0'; header_ptr += crlf_len; header_len -= buffer.consume( header_ptr-(char *)buffer ); all_headers = true; break; } } if ( crlf_len ) { if ( header_len == crlf_len ) { break; } else { *header_ptr = '\0'; header_ptr += crlf_len; header_len -= buffer.consume( header_ptr-(char *)buffer ); } } Debug( 6, "%s", header_ptr ); if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { //headers[n_headers++] = header_ptr; n_headers++; if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { http_header = header_ptr+http_match_len; Debug( 6, "Got http header '%s'", header_ptr ); } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { connection_header = header_ptr+connection_match_len; Debug( 6, "Got connection header '%s'", header_ptr ); } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { content_length_header = header_ptr+content_length_match_len; Debug( 6, "Got content length header '%s'", header_ptr ); } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { authenticate_header = header_ptr; Debug( 6, "Got authenticate header '%s'", header_ptr ); } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { content_type_header = header_ptr+content_type_match_len; Debug( 6, "Got content type header '%s'", header_ptr ); } else { Debug( 6, "Got ignored header '%s'", header_ptr ); } header_ptr = crlf; header_len -= buffer.consume( header_ptr-(char *)buffer ); } else { // No end of line found break; } } if ( all_headers ) { char *start_ptr, *end_ptr; if ( !http_header ) { Error( "Unable to extract HTTP status from header" ); return( -1 ); } start_ptr = http_header; end_ptr = start_ptr+strspn( start_ptr, "10." ); memset( http_version, 0, sizeof(http_version) ); strncpy( http_version, start_ptr, end_ptr-start_ptr ); start_ptr = end_ptr; start_ptr += strspn( start_ptr, " " ); end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); memset( status_code, 0, sizeof(status_code) ); strncpy( status_code, start_ptr, end_ptr-start_ptr ); int status = atoi( status_code ); start_ptr = end_ptr; start_ptr += strspn( start_ptr, " " ); strcpy( status_mesg, start_ptr ); if ( status == 401 ) { if ( mNeedAuth ) { Error( "Failed authentication: " ); return( -1 ); } if ( ! authenticate_header ) { Error( "Failed authentication, but don't have an authentication header: " ); return( -1 ); } mNeedAuth = true; std::string Header = authenticate_header; Debug(2, "Checking for digest auth in %s", authenticate_header ); mAuthenticator->checkAuthResponse(Header); if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { Debug( 2, "Need Digest Authentication" ); request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); if ( strcmp( config.http_version, "1.0" ) == 0 ) request += stringtf( "Connection: Keep-Alive\r\n" ); request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); request += "\r\n"; Debug( 2, "New request header: %s", request.c_str() ); return( 0 ); } else { Debug( 2, "Need some other kind of Authentication" ); } } else if ( status < 200 || status > 299 ) { Error( "Invalid response status %s: %s", status_code, status_mesg ); return( -1 ); } Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); if ( connection_header ) { memset( connection_type, 0, sizeof(connection_type) ); start_ptr = connection_header + strspn( connection_header, " " ); strcpy( connection_type, start_ptr ); Debug( 3, "Got connection '%s'", connection_type ); } if ( content_length_header ) { start_ptr = content_length_header + strspn( content_length_header, " " ); content_length = atoi( start_ptr ); Debug( 3, "Got content length '%d'", content_length ); } if ( content_type_header ) { memset( content_type, 0, sizeof(content_type) ); start_ptr = content_type_header + strspn( content_type_header, " " ); if ( (end_ptr = strchr( start_ptr, ';' )) ) { strncpy( content_type, start_ptr, end_ptr-start_ptr ); Debug( 3, "Got content type '%s'", content_type ); start_ptr = end_ptr + strspn( end_ptr, "; " ); if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { start_ptr += boundary_match_len; start_ptr += strspn( start_ptr, "-" ); content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); Debug( 3, "Got content boundary '%s'", content_boundary ); } else { Error( "No content boundary found in header '%s'", content_type_header ); } } else { strcpy( content_type, start_ptr ); Debug( 3, "Got content type '%s'", content_type ); } } if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { // Single image mode = SINGLE_IMAGE; format = JPEG; state = CONTENT; } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { // Single image mode = SINGLE_IMAGE; format = X_RGB; state = CONTENT; } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { // Single image mode = SINGLE_IMAGE; format = X_RGBZ; state = CONTENT; } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { // Image stream, so start processing if ( !content_boundary[0] ) { Error( "No content boundary found in header '%s'", content_type_header ); return( -1 ); } mode = MULTI_IMAGE; state = SUBHEADER; } //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) //{ //// MPEG stream, coming soon! //} else { Error( "Unrecognised content type '%s'", content_type ); return( -1 ); } } else { Debug( 3, "Unable to extract entire header from stream, continuing" ); state = HEADERCONT; //return( -1 ); } break; } case SUBHEADER : { n_subheaders = 0; boundary_header = 0; subcontent_length_header[0] = '\0'; subcontent_type_header[0] = '\0'; content_length = 0; content_type[0] = '\0'; } case SUBHEADERCONT : { char *crlf = 0; char *subheader_ptr = (char *)buffer; int subheader_len = buffer.size(); bool all_headers = false; while( true ) { int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); if ( n_subheaders ) { if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { *subheader_ptr = '\0'; subheader_ptr += crlf_len; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); all_headers = true; break; } } if ( crlf_len ) { if ( subheader_len == crlf_len ) { break; } else { *subheader_ptr = '\0'; subheader_ptr += crlf_len; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); } } Debug( 6, "%d: %s", subheader_len, subheader_ptr ); if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { //subheaders[n_subheaders++] = subheader_ptr; n_subheaders++; if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { boundary_header = subheader_ptr; Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); } else { Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); } subheader_ptr = crlf; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); } else { // No line end found break; } } if ( all_headers && boundary_header ) { char *start_ptr/*, *end_ptr*/; Debug( 3, "Got boundary '%s'", boundary_header ); if ( subcontent_length_header[0] ) { start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); content_length = atoi( start_ptr ); Debug( 3, "Got subcontent length '%d'", content_length ); } if ( subcontent_type_header[0] ) { memset( content_type, 0, sizeof(content_type) ); start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); strcpy( content_type, start_ptr ); Debug( 3, "Got subcontent type '%s'", content_type ); } state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); while ( ! ( buffer_len = ReadData( buffer ) ) ) { } if ( buffer_len < 0 ) { Error( "Unable to read subheader" ); return( -1 ); } state = SUBHEADERCONT; } break; } case CONTENT : { // if content_type is something like image/jpeg;size=, this will strip the ;size= char * semicolon = strchr( content_type, ';' ); if ( semicolon ) { *semicolon = '\0'; } if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { format = JPEG; } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { format = X_RGB; } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { format = X_RGBZ; } else { Error( "Found unsupported content type '%s'", content_type ); return( -1 ); } if ( format == JPEG && buffer.size() >= 2 ) { if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } } if ( content_length ) { while ( (long)buffer.size() < content_length ) { //int buffer_len = ReadData( buffer, content_length-buffer.size() ); if ( ReadData( buffer ) < 0 ) { Error( "Unable to read content" ); return( -1 ); } } Debug( 3, "Got end of image by length, content-length = %d", content_length ); } else { int content_pos = 0; while ( !content_length ) { buffer_len = ReadData( buffer ); if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } int buffer_size = buffer.size(); if ( buffer_len ) { if ( mode == MULTI_IMAGE ) { while ( char *start_ptr = (char *)memstr( (char *)buffer+content_pos, "\r\n--", buffer_size-content_pos ) ) { content_length = start_ptr - (char *)buffer; Debug( 3, "Got end of image by pattern (crlf--), content-length = %d", content_length ); break; } } } else { content_length = buffer_size; Debug( 3, "Got end of image by closure, content-length = %d", content_length ); if ( mode == SINGLE_IMAGE ) { char *end_ptr = (char *)buffer+buffer_size; while( *end_ptr == '\r' || *end_ptr == '\n' ) { content_length--; end_ptr--; } if ( end_ptr != ((char *)buffer+buffer_size) ) { Debug( 3, "Trimmed end of image, new content-length = %d", content_length ); } } } } } if ( mode == SINGLE_IMAGE ) { state = HEADER; Disconnect(); } else { state = SUBHEADER; } if ( format == JPEG && buffer.size() >= 2 ) { if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } } Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); return( content_length ); } } } } return( 0 ); }
void Condition::signal() { if ( pthread_cond_signal( &mCondition ) < 0 ) throw ThreadException( stringtf( "Unable to signal pthread condition: %s", strerror(errno) ) ); }
SessionDescriptor::SessionDescriptor( const std::string &url, const std::string &sdp ) : mUrl( url ), mConnInfo( 0 ), mBandInfo( 0 ) { MediaDescriptor *currMedia = 0; StringVector lines = split( sdp, "\r\n" ); for ( StringVector::const_iterator iter = lines.begin(); iter != lines.end(); iter++ ) { std::string line = *iter; if ( line.empty() ) break; Debug( 3, "Processing SDP line '%s'", line.c_str() ); const char sdpType = line[0]; if ( line[1] != '=' ) throw Exception( "Invalid SDP format at '"+line+"'" ); line.erase( 0, 2 ); switch( sdpType ) { case 'v' : mVersion = line; break; case 'o' : mOwner = line; break; case 's' : mName = line; break; case 'i' : mInfo = line; break; case 'c' : mConnInfo = new ConnInfo( line ); break; case 'b' : mBandInfo = new BandInfo( line ); break; case 't' : mTimeInfo = line; break; case 'a' : { mAttributes.push_back( line ); StringVector tokens = split( line, ":", 2 ); std::string attrName = tokens[0]; if ( currMedia ) { if ( attrName == "control" ) { if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP control attribute '"+line+"' for media '"+currMedia->getType()+"'" ); currMedia->setControlUrl( tokens[1] ); } else if ( attrName == "range" ) { } else if ( attrName == "rtpmap" ) { // a=rtpmap:96 MP4V-ES/90000 if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" ); StringVector attrTokens = split( tokens[1], " " ); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) ); std::string payloadDesc = attrTokens[1]; //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { StringVector payloadTokens = split( attrTokens[1], "/" ); std::string payloadDesc = payloadTokens[0]; int payloadClock = atoi(payloadTokens[1].c_str()); currMedia->setPayloadDesc( payloadDesc ); currMedia->setClock( payloadClock ); } } else if ( attrName == "framesize" ) { // a=framesize:96 320-240 if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'" ); StringVector attrTokens = split( tokens[1], " " ); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) ); //currMedia->setPayloadType( payloadType ); StringVector sizeTokens = split( attrTokens[1], "-" ); int width = atoi(sizeTokens[0].c_str()); int height = atoi(sizeTokens[1].c_str()); currMedia->setFrameSize( width, height ); } else if ( attrName == "framerate" ) { // a=framerate:5.0 if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP framerate attribute '"+line+"' for media '"+currMedia->getType()+"'" ); double frameRate = atof(tokens[1].c_str()); currMedia->setFrameRate( frameRate ); } else if ( attrName == "fmtp" ) { // a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'" ); StringVector attrTokens = split( tokens[1], " ", 2 ); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) ); //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { StringVector attr2Tokens = split( attrTokens[1], "; " ); for ( unsigned int i = 0; i < attr2Tokens.size(); i++ ) { StringVector attr3Tokens = split( attr2Tokens[i], "=" ); //Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() ); if ( attr3Tokens[0] == "profile-level-id" ) { } else if ( attr3Tokens[0] == "config" ) { } else { Debug( 3, "Ignoring SDP fmtp attribute '%s' for media '%s'", attr3Tokens[0].c_str(), currMedia->getType().c_str() ) } } } } else if ( attrName == "mpeg4-iod" ) { // a=mpeg4-iod: "data:application/mpeg4-iod;base64,AoEAAE8BAf73AQOAkwABQHRkYXRhOmFwcGxpY2F0aW9uL21wZWc0LW9kLWF1O2Jhc2U2NCxBVGdCR3dVZkF4Y0F5U1FBWlFRTklCRUVrK0FBQWEyd0FBR3RzQVlCQkFFWkFwOERGUUJsQlFRTlFCVUFDN2dBQVBvQUFBRDZBQVlCQXc9PQQNAQUABAAAAAAAAAAAAAYJAQAAAAAAAAAAA0IAAkA+ZGF0YTphcHBsaWNhdGlvbi9tcGVnNC1iaWZzLWF1O2Jhc2U2NCx3QkFTZ1RBcUJYSmhCSWhRUlFVL0FBPT0EEgINAAACAAAAAAAAAAAFAwAAQAYJAQAAAAAAAAAA" } else if ( attrName == "mpeg4-esid" ) { // a=mpeg4-esid:201 } else { Debug( 3, "Ignoring SDP attribute '%s' for media '%s'", line.c_str(), currMedia->getType().c_str() ) } } else { Debug( 3, "Ignoring general SDP attribute '%s'", line.c_str() ); } break; }
void Condition::broadcast() { if ( pthread_cond_broadcast( &mCondition ) < 0 ) throw ThreadException( stringtf( "Unable to broadcast pthread condition: %s", strerror(errno) ) ); }
std::string Authenticator::computeDigestResponse(std::string &method, std::string &uri) { #if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT // The "response" field is computed as: // md5(md5(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<url>)) size_t md5len = 16; unsigned char md5buf[md5len]; char md5HexBuf[md5len*2+1]; // Step 1: md5(<username>:<realm>:<password>) std::string ha1Data = username() + ":" + realm() + ":" + password(); Debug( 2, "HA1 pre-md5: %s", ha1Data.c_str() ); #if HAVE_DECL_MD5 MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); #elif HAVE_DECL_GNUTLS_FINGERPRINT gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), ha1Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; std::string ha1Hash = md5HexBuf; // Step 2: md5(<cmd>:<url>) std::string ha2Data = method + ":" + uri; Debug( 2, "HA2 pre-md5: %s", ha2Data.c_str() ); #if HAVE_DECL_MD5 MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); #elif HAVE_DECL_GNUTLS_FINGERPRINT gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), ha2Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; std::string ha2Hash = md5HexBuf; // Step 3: md5(ha1:<nonce>:ha2) std::string digestData = ha1Hash + ":" + nonce(); if ( ! fQop.empty() ) { digestData += ":" + stringtf("%08x", nc) + ":"+fCnonce + ":" + fQop; nc ++; // if qop was specified, then we have to include t and a cnonce and an nccount } digestData += ":" + ha2Hash; Debug( 2, "pre-md5: %s", digestData.c_str() ); #if HAVE_DECL_MD5 MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); #elif HAVE_DECL_GNUTLS_FINGERPRINT gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), digestData.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); } md5HexBuf[md5len*2]='\0'; return md5HexBuf; #else // HAVE_DECL_MD5 Error( "You need to build with gnutls or openssl installed to use digest authentication" ); return( 0 ); #endif // HAVE_DECL_MD5 }
/** * @brief * * @param requestType * @param requestUrl * @param requestHeaders * @param responseHeaders * * @return */ bool RtspSession::recvRequest( const std::string &requestType, const std::string &requestUrl, const Connection::Headers &requestHeaders, Connection::Headers &responseHeaders ) { Debug( 2, "Session %d - Processing RTSP request: %s", mSession, requestType.c_str() ); if ( requestType == "SETUP" ) { std::string requestHost, requestStreamName, requestStreamSource; int trackId = -1; FeedProvider *provider = mConnection->validateRequestUrl( requestUrl, &requestHost, &requestStreamName, &requestStreamSource, &trackId ); if ( !provider ) { mConnection->sendResponse( responseHeaders, "", 404, "Not Found" ); return( false ); } if ( !provider->ready() ) { mConnection->sendResponse( responseHeaders, "", 503, "Stream not ready" ); return( false ); } if ( trackId < 0 || trackId > 1 ) { Error( "Got invalid trackID %d from request URL '%s'", trackId, requestUrl.c_str() ); return( false ); } if ( mRtspStreams.find( trackId ) != mRtspStreams.end() ) { Error( "Got RTSP SETUP for existing trackID %d", trackId ); return( false ); } Connection::Headers::const_iterator iter = requestHeaders.find("Transport"); if ( iter == requestHeaders.end() ) { Error( "No Transport header found in SETUP" ); return( false ); } std::string transportSpec = iter->second; RtspStream *stream = newStream( trackId, mEncoder, transportSpec ); transportSpec.clear(); transportSpec += "RTP/AVP"; if ( stream->lowerTransport() == RtspStream::LOWTRANS_TCP ) { transportSpec += "/TCP"; } else { transportSpec += "/UDP"; if ( stream->delivery() == RtspStream::DEL_UNICAST ) transportSpec += ";unicast"; else transportSpec += ";multicast"; } if ( stream->interleaved() ) { transportSpec += stringtf( ";interleaved=%d-%d", stream->channel(0), stream->channel(1) ); } else { transportSpec += stringtf( ";client_port=%d-%d", stream->destPort(0), stream->destPort(1) ); transportSpec += stringtf( ";server_port=%d-%d", stream->sourcePort(0), stream->sourcePort(1) ); } transportSpec += stringtf( ";ssrc=%08X", stream->ssrc() ); transportSpec += ";mode=\"PLAY\""; responseHeaders.insert( Connection::Headers::value_type( "Session", stringtf( "%08X", mSession ) ) ); responseHeaders.insert( Connection::Headers::value_type( "Transport", transportSpec ) ); state( READY ); return( true ); } else if ( requestType == "PLAY" ) { std::string rtpInfo; for ( RtspStreams::iterator iter = mRtspStreams.begin(); iter != mRtspStreams.end(); iter++ ) { const RtspStream *stream = iter->second; if ( rtpInfo.size() ) rtpInfo += ","; rtpInfo += stringtf( "url=%s/trackID=%d;seq=0;rtptime=0", requestUrl.c_str(), stream->trackId() ); } responseHeaders.insert( Connection::Headers::value_type( "Session", stringtf( "%08X", mSession ) ) ); responseHeaders.insert( Connection::Headers::value_type( "Range", "npt=0-" ) ); responseHeaders.insert( Connection::Headers::value_type( "RTP-Info", rtpInfo ) ); for ( RtspStreams::iterator iter = mRtspStreams.begin(); iter != mRtspStreams.end(); iter++ ) iter->second->start(); state( PLAYING ); return( true ); } else if ( requestType == "GET_PARAMETER" ) { FeedProvider *provider = mConnection->validateRequestUrl( requestUrl ); if ( provider ) { responseHeaders.insert( Connection::Headers::value_type( "Session", stringtf( "%08X", mSession ) ) ); return( true ); } mConnection->sendResponse( responseHeaders, "", 451, "Parameter Not Understood" ); return( false ); } else if ( requestType == "TEARDOWN" ) { Debug( 1, "Stopping streams" ); for ( RtspStreams::iterator iter = mRtspStreams.begin(); iter != mRtspStreams.end(); iter++ ) iter->second->stop(); Debug( 1, "Waiting for streams" ); for ( RtspStreams::iterator iter = mRtspStreams.begin(); iter != mRtspStreams.end(); iter++ ) iter->second->join(); for ( RtspStreams::iterator iter = mRtspStreams.begin(); iter != mRtspStreams.end(); iter++ ) delete iter->second; mRtspStreams.clear(); state( INIT ); return( true ); } Error( "Unrecognised RTSP command '%s'", requestType.c_str() ); return( false ); }