diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 9c0de2a..2ad184e 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,11 +4,16 @@ "name": "Linux", "includePath": [ "${workspaceFolder}/**", - "/usr/include/libcamera" + "/usr/include/libcamera", + "/usr/include/liveMedia", + "/usr/include/groupsock", + "/usr/include/BasicUsageEnvironment", + "/usr/include/UsageEnvironment" ], "defines": [ "USE_LIBCAMERA=1", - "USE_FFMPEG=1" + "USE_FFMPEG=1", + "USE_RTSP=1" ], "compilerPath": "/usr/bin/gcc", "cStandard": "gnu17", diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e9745b..212fb7a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -78,6 +78,8 @@ "opts.h": "c", "ioctl.h": "c", "syscall.h": "c", - "unistd.h": "c" + "unistd.h": "c", + "rtsp.h": "c", + "http.h": "c" } } diff --git a/Makefile b/Makefile index edbc084..ea9d1ad 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ endif USE_FFMPEG ?= $(shell pkg-config libavutil libavformat libavcodec && echo 1) USE_LIBCAMERA ?= $(shell pkg-config libcamera && echo 1) +USE_RTSP ?= $(shell pkg-config live555 && echo 1) ifeq (1,$(DEBUG)) CFLAGS += -g @@ -27,6 +28,11 @@ CFLAGS += -DUSE_LIBCAMERA $(shell pkg-config --cflags libcamera) LDLIBS += $(shell pkg-config --libs libcamera) endif +ifeq (1,$(USE_RTSP)) +CFLAGS += -DUSE_RTSP $(shell pkg-config --cflags live555) +LDLIBS += $(shell pkg-config --libs live555) +endif + HTML_SRC = $(addsuffix .c,$(HTML)) OBJS = $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(SRC) $(HTML_SRC))) diff --git a/cmd/camera-streamer.c b/cmd/camera-streamer.c index 45962b8..c1bdd5f 100644 --- a/cmd/camera-streamer.c +++ b/cmd/camera-streamer.c @@ -3,6 +3,7 @@ #include "opts/log.h" #include "opts/fourcc.h" #include "device/camera/camera.h" +#include "rtsp/rtsp.h" #include #include @@ -213,6 +214,8 @@ int main(int argc, char *argv[]) goto error; } + rtsp_server(); + while (true) { camera = camera_open(&camera_options); if (camera) { diff --git a/device/camera/camera_output.c b/device/camera/camera_output.c index ea822cb..edf2235 100644 --- a/device/camera/camera_output.c +++ b/device/camera/camera_output.c @@ -8,6 +8,7 @@ #include "opts/fourcc.h" #include "device/buffer_list.h" #include "http/http.h" +#include "rtsp/rtsp.h" static const char *jpeg_names[2] = { "JPEG", @@ -24,8 +25,19 @@ static const char *h264_names[2] = { "H264-LOW" }; +static void h264_capture(buffer_t *buf) +{ + http_h264_capture(buf); + rtsp_h264_capture(buf); +} + +static bool h264_needs_buffer() +{ + return http_h264_needs_buffer() | rtsp_h264_needs_buffer(); +} + static link_callbacks_t h264_callbacks[2] = { - { "H264-CAPTURE", http_h264_capture, http_h264_needs_buffer }, + { "H264-CAPTURE", h264_capture, h264_needs_buffer }, { "H264-LOW-CAPTURE", http_h264_lowres_capture, http_h264_needs_buffer } }; diff --git a/rtsp/rtsp.cc b/rtsp/rtsp.cc new file mode 100644 index 0000000..3ed123b --- /dev/null +++ b/rtsp/rtsp.cc @@ -0,0 +1,258 @@ +extern "C" { + +#include "http/http.h" +#include "device/buffer.h" +#include "device/buffer_list.h" +#include "device/device.h" +#include "opts/log.h" +#include "opts/fourcc.h" +#include "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; + +class DynamicH264Stream : public FramedSource +{ +public: + DynamicH264Stream(UsageEnvironment& env) : FramedSource(env), fHaveStartedReading(False) + { + } + + 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) + { + if (!isCurrentlyAwaitingData()) { + return; // we're not ready for the data yet + } + + 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; + +public: + DynamicH264Stream *pNextStream; +}; + +class DynamicH264VideoFileServerMediaSubsession : public OnDemandServerMediaSubsession +{ +public: + DynamicH264VideoFileServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource) + : OnDemandServerMediaSubsession(env, reuseFirstSource) + { + } + + virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) + { + estBitrate = 500; // kbps, estimate + return H264VideoStreamFramer::createNew(envir(), new DynamicH264Stream(envir())); + } + + virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* /*inputSource*/) + { + return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); + } +}; + +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) + { + LOG_INFO(NULL, "Requesting %s stream...", streamName); + + 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); + sms->addSubsession(subsession); + addServerMediaSession(sms); + + LOG_INFO(NULL, "StreamName=%s SMS=%p Sub=%p", streamName, sms, subsession); + 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; + portNumBits rtspServerPortNum = 554; + rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB); + if (rtspServer == NULL) { + rtspServerPortNum = 8554; + rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, 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'", rtspServerPortNum); + + // 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); + } + 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) +{ +} + +#endif // USE_RTSP \ No newline at end of file diff --git a/rtsp/rtsp.h b/rtsp/rtsp.h new file mode 100644 index 0000000..1b9d26c --- /dev/null +++ b/rtsp/rtsp.h @@ -0,0 +1,5 @@ +#pragma once + +int rtsp_server(); +bool rtsp_h264_needs_buffer(); +void rtsp_h264_capture(struct buffer_s *buf);