Add RTSP support via live555
This commit is contained in:
parent
7a584dc16d
commit
d49169070a
9
.vscode/c_cpp_properties.json
vendored
9
.vscode/c_cpp_properties.json
vendored
@ -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",
|
||||
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
|
6
Makefile
6
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)))
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "opts/log.h"
|
||||
#include "opts/fourcc.h"
|
||||
#include "device/camera/camera.h"
|
||||
#include "rtsp/rtsp.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
@ -213,6 +214,8 @@ int main(int argc, char *argv[])
|
||||
goto error;
|
||||
}
|
||||
|
||||
rtsp_server();
|
||||
|
||||
while (true) {
|
||||
camera = camera_open(&camera_options);
|
||||
if (camera) {
|
||||
|
@ -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 }
|
||||
};
|
||||
|
||||
|
258
rtsp/rtsp.cc
Normal file
258
rtsp/rtsp.cc
Normal file
@ -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 <BasicUsageEnvironment.hh>
|
||||
#include <RTSPServerSupportingHTTPStreaming.hh>
|
||||
#include <OnDemandServerMediaSubsession.hh>
|
||||
#include <H264VideoStreamFramer.hh>
|
||||
#include <H264VideoRTPSink.hh>
|
||||
|
||||
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 <username>, <password> 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
|
5
rtsp/rtsp.h
Normal file
5
rtsp/rtsp.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
int rtsp_server();
|
||||
bool rtsp_h264_needs_buffer();
|
||||
void rtsp_h264_capture(struct buffer_s *buf);
|
Loading…
x
Reference in New Issue
Block a user