extern "C" { #include "http/http.h" #include "device/buffer.h" #include "device/buffer_list.h" #include "device/device.h" #include "util/opts/log.h" #include "util/opts/fourcc.h" #include "util/opts/control.h" #include "rtsp.h" }; #ifdef USE_RTSP #include #include #include #include #include static pthread_t rtsp_thread; static pthread_mutex_t rtsp_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; static class DynamicH264Stream *rtsp_streams; static const char *stream_name = "stream.h264"; static const char *stream_low_res_name = "stream_low_res.h264"; class DynamicH264Stream : public FramedSource { public: DynamicH264Stream(UsageEnvironment& env, Boolean lowResMode) : FramedSource(env), fHaveStartedReading(False), fLowResMode(lowResMode) { } void doGetNextFrame() { pthread_mutex_lock(&rtsp_lock); if (!fHaveStartedReading) { pNextStream = rtsp_streams; rtsp_streams = this; fHaveStartedReading = True; } pthread_mutex_unlock(&rtsp_lock); } void doStopGettingFrames() { pthread_mutex_lock(&rtsp_lock); if (fHaveStartedReading) { DynamicH264Stream **streamp = &rtsp_streams; while (*streamp) { if (*streamp == this) { *streamp = pNextStream; pNextStream = NULL; break; } streamp = &(*streamp)->pNextStream; } fHaveStartedReading = False; } pthread_mutex_unlock(&rtsp_lock); } void receiveData(buffer_t *buf, bool lowResMode) { if (!isCurrentlyAwaitingData()) { return; // we're not ready for the data yet } if (fLowResMode != lowResMode) { return; } if (h264_is_key_frame(buf)) { fHadKeyFrame = true; } if (!fRequestedKeyFrame) { if (!fHadKeyFrame) { printf("device_video_force_key: %p\n", this); device_video_force_key(buf->buf_list->dev); } fRequestedKeyFrame = true; } if (!fHadKeyFrame) { return; } if (buf->used > fMaxSize) { fNumTruncatedBytes = buf->used - fMaxSize; fFrameSize = fMaxSize; } else { fNumTruncatedBytes = 0; fFrameSize = buf->used; } memcpy(fTo, buf->start, fFrameSize); // Tell our client that we have new data: afterGetting(this); // we're preceded by a net read; no infinite recursion } private: Boolean fHaveStartedReading; Boolean fHadKeyFrame; Boolean fRequestedKeyFrame; Boolean fLowResMode; public: DynamicH264Stream *pNextStream; }; class DynamicH264VideoFileServerMediaSubsession : public OnDemandServerMediaSubsession { public: DynamicH264VideoFileServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource, Boolean lowResMode) : OnDemandServerMediaSubsession(env, reuseFirstSource), fLowResMode(lowResMode) { } virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) { estBitrate = 500; // kbps, estimate return H264VideoStreamFramer::createNew(envir(), new DynamicH264Stream(envir(), fLowResMode)); } virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) { return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); } private: Boolean fLowResMode; }; class DynamicRTSPServer: public RTSPServerSupportingHTTPStreaming { public: static DynamicRTSPServer* createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds = 65) { int ourSocket = setUpOurSocket(env, ourPort); if (ourSocket == -1) return NULL; return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds); } protected: DynamicRTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds) : RTSPServerSupportingHTTPStreaming(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds) { } // called only by createNew(); virtual ~DynamicRTSPServer() { } protected: // redefined virtual functions virtual ServerMediaSession* lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession) { bool lowResMode = false; if (strcmp(streamName, stream_name) == 0) { LOG_INFO(NULL, "Requesting %s stream...", streamName); } else if (strcmp(streamName, stream_low_res_name) == 0) { LOG_INFO(NULL, "Requesting %s stream (low resolution mode)...", streamName); lowResMode = true; } else { LOG_INFO(NULL, "No stream available: '%s'", streamName); return NULL; } auto sms = RTSPServer::lookupServerMediaSession(streamName); if (sms && isFirstLookupInSession) { // Remove the existing "ServerMediaSession" and create a new one, in case the underlying // file has changed in some way: removeServerMediaSession(sms); sms = NULL; } sms = ServerMediaSession::createNew(envir(), streamName, streamName, "streamed by the LIVE555 Media Server");; OutPacketBuffer::maxSize = 2000000; // allow for some possibly large H.264 frames auto subsession = new DynamicH264VideoFileServerMediaSubsession(envir(), false, lowResMode); sms->addSubsession(subsession); addServerMediaSession(sms); return sms; } }; static void *rtsp_server_thread(void *opaque) { UsageEnvironment* env = (UsageEnvironment*)opaque; env->taskScheduler().doEventLoop(); // does not return return NULL; } extern "C" int rtsp_server() { // Begin by setting up our usage environment: TaskScheduler* scheduler = BasicTaskScheduler::createNew(); UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); UserAuthenticationDatabase* authDB = NULL; #ifdef ACCESS_CONTROL // To implement client access control to the RTSP server, do the following: authDB = new UserAuthenticationDatabase; authDB->addUserRecord("username1", "password1"); // replace these with real strings // Repeat the above with each , that you wish to allow // access to the server. #endif RTSPServer* rtspServer; rtspServer = DynamicRTSPServer::createNew(*env, rtsp_options.port, authDB); if (rtspServer == NULL) { LOG_ERROR(NULL, "Failed to create RTSP server: %s", env->getResultMsg()); return -1; } LOG_INFO(NULL, "Running RTSP server on '%d'", rtsp_options.port); // if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) { // LOG_INFO(NULL, "Running RTSP-over-HTTP tunneling on '%d'", rtspServer->httpServerPortNum()); // *env << "(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)\n"; // } else { // LOG_INFO(NULL, "The RTSP-over-HTTP is not available."); // } pthread_create(&rtsp_thread, NULL, rtsp_server_thread, env); return 0; error: return -1; } extern "C" bool rtsp_h264_needs_buffer() { return rtsp_streams != NULL; } extern "C" void rtsp_h264_capture(buffer_t *buf) { pthread_mutex_lock(&rtsp_lock); for (DynamicH264Stream *stream = rtsp_streams; stream; stream = stream->pNextStream) { stream->receiveData(buf, false); } pthread_mutex_unlock(&rtsp_lock); } extern "C" void rtsp_h264_low_res_capture(struct buffer_s *buf) { pthread_mutex_lock(&rtsp_lock); for (DynamicH264Stream *stream = rtsp_streams; stream; stream = stream->pNextStream) { stream->receiveData(buf, true); } pthread_mutex_unlock(&rtsp_lock); } #else // USE_RTSP extern "C" int rtsp_server() { return 0; } extern "C" bool rtsp_h264_needs_buffer() { return false; } extern "C" void rtsp_h264_capture(buffer_t *buf) { } extern "C" void rtsp_h264_low_res_capture(struct buffer_s *buf) { } #endif // USE_RTSP