From 6159bbb27d40d72062c256d6a97f5f785ab13e27 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Sun, 22 Dec 2024 19:10:44 +0800 Subject: [PATCH] fix live555 reference not found and use new project construction --- live555.lua | 37 +- live555_local.lua | 49 --- src/{ => modules}/isp/eb3516_video.c | 0 src/{ => modules}/isp/eb3516_video.h | 0 src/{ => modules}/nnie/eb3516_nnie.c | 0 src/{ => modules}/nnie/eb3516_nnie.h | 0 .../nnie/sample_svp_nnie_software.c | 0 .../nnie/sample_svp_nnie_software.h | 0 src/modules/rtsp/BaseDeviceSource.hh | 22 ++ src/modules/rtsp/JpegFrameParser.cpp | 369 ++++++++++++++++++ src/modules/rtsp/JpegFrameParser.hh | 81 ++++ .../rtsp/LiveServerMediaSubsession.cpp | 75 ++++ src/modules/rtsp/LiveServerMediaSubsession.hh | 32 ++ src/modules/rtsp/MJPEGDeviceSource.cpp | 95 +++++ src/modules/rtsp/MJPEGDeviceSource.hh | 51 +++ src/modules/rtsp/build.mk | 24 ++ src/modules/rtsp/rtsp_server.cpp | 132 +++++++ xmake.lua | 59 +-- 18 files changed, 951 insertions(+), 75 deletions(-) delete mode 100644 live555_local.lua rename src/{ => modules}/isp/eb3516_video.c (100%) rename src/{ => modules}/isp/eb3516_video.h (100%) rename src/{ => modules}/nnie/eb3516_nnie.c (100%) rename src/{ => modules}/nnie/eb3516_nnie.h (100%) rename src/{ => modules}/nnie/sample_svp_nnie_software.c (100%) rename src/{ => modules}/nnie/sample_svp_nnie_software.h (100%) create mode 100755 src/modules/rtsp/BaseDeviceSource.hh create mode 100755 src/modules/rtsp/JpegFrameParser.cpp create mode 100755 src/modules/rtsp/JpegFrameParser.hh create mode 100755 src/modules/rtsp/LiveServerMediaSubsession.cpp create mode 100755 src/modules/rtsp/LiveServerMediaSubsession.hh create mode 100755 src/modules/rtsp/MJPEGDeviceSource.cpp create mode 100755 src/modules/rtsp/MJPEGDeviceSource.hh create mode 100755 src/modules/rtsp/build.mk create mode 100755 src/modules/rtsp/rtsp_server.cpp diff --git a/live555.lua b/live555.lua index 5cab9e2..953e5a7 100644 --- a/live555.lua +++ b/live555.lua @@ -1,11 +1,23 @@ package("live555") +set_kind("library") set_homepage("http://www.live555.com") set_urls("http://www.live555.com/liveMedia/public/live.$(version).tar.gz") add_versions("2024.11.28", "a9af16f46d2f4c7ccdbfc4b617480503d27cccb46fa5abb7dfd8a25951b44cc3") +add_configs("no_openssl", {description = "Set 1 if no OpenSSL", default = "1", values = {"0", "1"}}) +add_configs("no_std_lib", {description = "Set 1 if no C++20 STD LIB", default = "1", values = {"0", "1"}}) + +add_includedirs( + "include/BasicUsageEnvironment", + "include/groupsock", + "include/liveMedia", + "include/UsageEnvironment" +) + +local compile_opts = "COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 " + local make_scipt = [[ -COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -DNO_OPENSSL=1 -DNO_STD_LIB=1 C = c C_COMPILER = cc C_FLAGS = $(COMPILE_OPTS) $(CPPFLAGS) $(CFLAGS) @@ -24,16 +36,32 @@ LIBS_FOR_GUI_APPLICATION = EXE = ]] +on_load(function (package) + local no_openssl = package:config("no_openssl") or "1" + local no_std_lib = package:config("no_std_lib") or "1" + package:add("defines", "NO_OPENSSL=" .. no_openssl) + package:add("defines", "NO_STD_LIB=" .. no_std_lib) +end) + on_install(function(package) -- Create plat make script + local no_openssl = package:config("no_openssl") or "1" + local no_std_lib = package:config("no_std_lib") or "1" local script = io.open("config.cross", "w") if script then + script:write(compile_opts) + script:print("-DNO_OPENSSL=%s -DNO_STD_LIB=%s", no_openssl, no_std_lib) script:write(make_scipt) script:print("PREFIX = %s", package:installdir()) script:close() end - -- Don't forget to append space at the end of line "LIBRARY_LINK" + if no_openssl == "0" then + os.vrun("sed -i 's/LIBS_FOR_CONSOLE_APPLICATION =/& -lssl -lcrypto/g' config.cross") + end + --! Don't forget to append space at the end of line "LIBRARY_LINK" os.vrun("sed -i 's/ar cr/ar cr /g' config.cross") + os.vrun("echo ----- Compile Opts -----") + os.vrun("cat config.cross") -- Generate makefile os.vrun("chmod +rw genMakefiles") @@ -44,3 +72,8 @@ on_install(function(package) os.vrun("make clean") import("package.tools.make").install(package) end) + +on_test(function (package) + assert(package:has_cxxtypes("RTSPServer", {includes = "liveMedia.hh"})) +end) + diff --git a/live555_local.lua b/live555_local.lua deleted file mode 100644 index d7a4ea6..0000000 --- a/live555_local.lua +++ /dev/null @@ -1,49 +0,0 @@ -package("live555") --- set_homepage("http://www.live555.com") --- --- set_urls("http://www.live555.com/liveMedia/public/live.$(version).tar.gz") --- add_versions("2024.11.28", "a9af16f46d2f4c7ccdbfc4b617480503d27cccb46fa5abb7dfd8a25951b44cc3") - -set_sourcedir(path.join(os.projectdir(), "Rouring/thirdparty/live555")) - -local make_scipt = [[ -COMPILE_OPTS = $(INCLUDES) -I. -O2 -DSOCKLEN_T=socklen_t -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 -DNO_OPENSSL=1 -C = c -C_COMPILER = cc -C_FLAGS = $(COMPILE_OPTS) $(CPPFLAGS) $(CFLAGS) -CPP = cpp -CPLUSPLUS_COMPILER = c++ -CPLUSPLUS_FLAGS = $(COMPILE_OPTS) -Wall -DBSD=1 $(CPPFLAGS) $(CXXFLAGS) -OBJ = o -LINK = c++ -o -LINK_OPTS = -L. $(LDFLAGS) -CONSOLE_LINK_OPTS = $(LINK_OPTS) -LIBRARY_LINK = ar cr -LIBRARY_LINK_OPTS = -LIB_SUFFIX = a -LIBS_FOR_CONSOLE_APPLICATION = -LIBS_FOR_GUI_APPLICATION = -EXE = -]] - -on_install(function(package) - -- Create plat make script - local script = io.open("config.cross", "w") - if script then - script:write(make_scipt) - script:print("PREFIX = %s", package:installdir()) - script:close() - end - -- Don't forget to append space at the end of line "LIBRARY_LINK" - os.vrun("sed -i 's/ar cr/ar cr /g' config.cross") - - -- Generate makefile - os.vrun("chmod +rw genMakefiles") - os.vrun("chmod ugo+rwx config.cross") - os.vrun("sed -i 's/\\/bin\\/rm/rm/g' genMakefiles") - os.vrun("./genMakefiles cross") - - -- Install - os.vrun("make clean") - import("package.tools.make").install(package) -end) diff --git a/src/isp/eb3516_video.c b/src/modules/isp/eb3516_video.c similarity index 100% rename from src/isp/eb3516_video.c rename to src/modules/isp/eb3516_video.c diff --git a/src/isp/eb3516_video.h b/src/modules/isp/eb3516_video.h similarity index 100% rename from src/isp/eb3516_video.h rename to src/modules/isp/eb3516_video.h diff --git a/src/nnie/eb3516_nnie.c b/src/modules/nnie/eb3516_nnie.c similarity index 100% rename from src/nnie/eb3516_nnie.c rename to src/modules/nnie/eb3516_nnie.c diff --git a/src/nnie/eb3516_nnie.h b/src/modules/nnie/eb3516_nnie.h similarity index 100% rename from src/nnie/eb3516_nnie.h rename to src/modules/nnie/eb3516_nnie.h diff --git a/src/nnie/sample_svp_nnie_software.c b/src/modules/nnie/sample_svp_nnie_software.c similarity index 100% rename from src/nnie/sample_svp_nnie_software.c rename to src/modules/nnie/sample_svp_nnie_software.c diff --git a/src/nnie/sample_svp_nnie_software.h b/src/modules/nnie/sample_svp_nnie_software.h similarity index 100% rename from src/nnie/sample_svp_nnie_software.h rename to src/modules/nnie/sample_svp_nnie_software.h diff --git a/src/modules/rtsp/BaseDeviceSource.hh b/src/modules/rtsp/BaseDeviceSource.hh new file mode 100755 index 0000000..b20bb06 --- /dev/null +++ b/src/modules/rtsp/BaseDeviceSource.hh @@ -0,0 +1,22 @@ +/** + * @file BaseDeviceSource.hh + * @author 吴晨 + * @brief 适配 live555 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#ifndef _BASE_DEVICE_SOURCE_HH +#define _BASE_DEVICE_SOURCE_HH + +#include "msgbus/ipc_msg.h" + +using FrameData = mpp_packet_t; + +class BaseDeviceSource { +public: + virtual codec_type_t getCodecType() = 0; + virtual void signalNewFrame(const FrameData &frameData) = 0; +}; + +#endif diff --git a/src/modules/rtsp/JpegFrameParser.cpp b/src/modules/rtsp/JpegFrameParser.cpp new file mode 100755 index 0000000..c20b052 --- /dev/null +++ b/src/modules/rtsp/JpegFrameParser.cpp @@ -0,0 +1,369 @@ +/* + * Copyright (C) Peter Gaal + * this code is derived from work of W.L. Chuang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include + +#include "JpegFrameParser.hh" + +#ifndef NDEBUG + #include + #define LOGGY(format, ...) fprintf (stderr, format, ##__VA_ARGS__) +#else + #define LOGGY(format, ...) +#endif /* NDEBUG */ + + +enum +{ + START_MARKER = 0xFF, + SOI_MARKER = 0xD8, + JFIF_MARKER = 0xE0, + CMT_MARKER = 0xFE, + DQT_MARKER = 0xDB, + SOF_MARKER = 0xC0, + DHT_MARKER = 0xC4, + SOS_MARKER = 0xDA, + EOI_MARKER = 0xD9, + DRI_MARKER = 0xDD +}; + +typedef struct +{ + unsigned char id; + unsigned char samp; + unsigned char qt; +} CompInfo; + + +JpegFrameParser::JpegFrameParser() : + _width(0), _height(0), _type(0), + _precision(0), _qFactor(255), + _qTables(NULL), _qTablesLength(0), + _restartInterval(0), + _scandata(NULL), _scandataLength(0) +{ + _qTables = new unsigned char[128 * 2]; + memset(_qTables, 8, 128 * 2); +} + +JpegFrameParser::~JpegFrameParser() +{ + if (_qTables != NULL) delete[] _qTables; +} + +unsigned int JpegFrameParser::scanJpegMarker(const unsigned char* data, + unsigned int size, + unsigned int* offset) +{ + while ((data[(*offset)++] != START_MARKER) && ((*offset) < size)); + + if ((*offset) >= size) { + return EOI_MARKER; + } else { + unsigned int marker; + + marker = data[*offset]; + (*offset)++; + + return marker; + } +} + +static unsigned int _jpegHeaderSize(const unsigned char* data, unsigned int offset) +{ + return data[offset] << 8 | data[offset + 1]; +} + +int JpegFrameParser::readSOF(const unsigned char* data, unsigned int size, + unsigned int* offset) +{ + int i, j; + CompInfo elem; + CompInfo info[3] = { {0,}, }; + unsigned int sof_size, off; + unsigned int width, height, infolen; + + off = *offset; + + /* we need at least 17 bytes for the SOF */ + if (off + 17 > size) goto wrong_size; + + sof_size = _jpegHeaderSize(data, off); + if (sof_size < 17) goto wrong_length; + + *offset += sof_size; + + /* skip size */ + off += 2; + + /* precision should be 8 */ + if (data[off++] != 8) goto bad_precision; + + /* read dimensions */ + height = data[off] << 8 | data[off + 1]; + width = data[off + 2] << 8 | data[off + 3]; + off += 4; + + if (height == 0 || height > 2040) goto invalid_dimension; + if (width == 0 || width > 2040) goto invalid_dimension; + + _width = width / 8; + _height = height / 8; + + /* we only support 3 components */ + if (data[off++] != 3) goto bad_components; + + infolen = 0; + for (i = 0; i < 3; i++) { + elem.id = data[off++]; + elem.samp = data[off++]; + elem.qt = data[off++]; + + /* insertion sort from the last element to the first */ + for (j = infolen; j > 1; j--) { + if (info[j - 1].id < elem.id) break; + info[j] = info[j - 1]; + } + info[j] = elem; + infolen++; + } + + /* see that the components are supported */ + if (info[0].samp == 0x21) { + _type = 0; + } else if (info[0].samp == 0x22) { + _type = 1; + } else { + goto invalid_comp; + } + + if (!(info[1].samp == 0x11)) goto invalid_comp; + if (!(info[2].samp == 0x11)) goto invalid_comp; + // if (info[1].qt != info[2].qt) goto invalid_comp; + + return 0; + + /* ERRORS */ +wrong_size: + LOGGY("Wrong SOF size\n"); + return -1; + +wrong_length: + LOGGY("Wrong SOF length\n"); + return -1; + +bad_precision: + LOGGY("Bad precision\n"); + return -1; + +invalid_dimension: + LOGGY("Invalid dimension\n"); + return -1; + +bad_components: + LOGGY("Bad component\n"); + return -1; + +invalid_comp: + LOGGY("Invalid component\n"); + return -1; +} + +unsigned int JpegFrameParser::readDQT(const unsigned char* data, + unsigned int size, + unsigned int offset) +{ + unsigned int quant_size, tab_size; + unsigned char prec; + unsigned char id; + + if (offset + 2 > size) goto too_small; + + quant_size = _jpegHeaderSize(data, offset); + if (quant_size < 2) goto small_quant_size; + + /* clamp to available data */ + if (offset + quant_size > size) { + quant_size = size - offset; + } + + offset += 2; + quant_size -= 2; + + while (quant_size > 0) { + /* not enough to read the id */ + if (offset + 1 > size) break; + + id = data[offset] & 0x0f; + if (id == 15) goto invalid_id; + + prec = (data[offset] & 0xf0) >> 4; + if (prec) { + tab_size = 128; + _qTablesLength = 128 * 2; + } else { + tab_size = 64; + _qTablesLength = 64 * 2; + } + + /* there is not enough for the table */ + if (quant_size < tab_size + 1) goto no_table; + + //LOGGY("Copy quantization table: %u\n", id); + memcpy(&_qTables[id * tab_size], &data[offset + 1], tab_size); + + tab_size += 1; + quant_size -= tab_size; + offset += tab_size; + } + +done: + return offset + quant_size; + + /* ERRORS */ +too_small: + LOGGY("DQT is too small\n"); + return size; + +small_quant_size: + LOGGY("Quantization table is too small\n"); + return size; + +invalid_id: + LOGGY("Invalid table ID\n"); + goto done; + +no_table: + LOGGY("table doesn't exist\n"); + goto done; +} + +int JpegFrameParser::readDRI(const unsigned char* data, + unsigned int size, unsigned int* offset) +{ + unsigned int dri_size, off; + + off = *offset; + + /* we need at least 4 bytes for the DRI */ + if (off + 4 > size) goto wrong_size; + + dri_size = _jpegHeaderSize(data, off); + if (dri_size < 4) goto wrong_length; + + *offset += dri_size; + off += 2; + + _restartInterval = (data[off] << 8) | data[off + 1]; + + return 0; + +wrong_size: + return -1; + +wrong_length: + *offset += dri_size; + return -1; +} + +int JpegFrameParser::parse(unsigned char* data, unsigned int size) +{ + _width = 0; + _height = 0; + _type = 0; + _precision = 0; + //_qFactor = 0; + _restartInterval = 0, + + _scandata = NULL; + _scandataLength = 0; + + unsigned int offset = 0; + unsigned int dqtFound = 0; + unsigned int sosFound = 0; + unsigned int sofFound = 0; + unsigned int driFound = 0; + unsigned int jpeg_header_size = 0; + + while ((sosFound == 0) && (offset < size)) { + switch (scanJpegMarker(data, size, &offset)) { + case JFIF_MARKER: + case CMT_MARKER: + case DHT_MARKER: + offset += _jpegHeaderSize(data, offset); + break; + case SOF_MARKER: + if (readSOF(data, size, &offset) != 0) { + goto invalid_format; + } + sofFound = 1; + break; + case DQT_MARKER: + offset = readDQT(data, size, offset); + dqtFound = 1; + break; + case SOS_MARKER: + sosFound = 1; + jpeg_header_size = offset + _jpegHeaderSize(data, offset); + break; + case EOI_MARKER: + /* EOI reached before SOS!? */ + LOGGY("EOI reached before SOS!?\n"); + break; + case SOI_MARKER: + //LOGGY("SOI found\n"); + break; + case DRI_MARKER: + LOGGY("DRI found\n"); + if (readDRI(data, size, &offset) == 0) { + driFound = 1; + } + break; + default: + break; + } + } + if ((dqtFound == 0) || (sofFound == 0)) { + goto unsupported_jpeg; + } + + if (_width == 0 || _height == 0) { + goto no_dimension; + } + + _scandata = data + jpeg_header_size; + _scandataLength = size - jpeg_header_size; + + if (driFound == 1) { + _type += 64; + } + + return 0; + + /* ERRORS */ +unsupported_jpeg: + return -1; + +no_dimension: + return -1; + +invalid_format: + return -1; +} diff --git a/src/modules/rtsp/JpegFrameParser.hh b/src/modules/rtsp/JpegFrameParser.hh new file mode 100755 index 0000000..d14c1bc --- /dev/null +++ b/src/modules/rtsp/JpegFrameParser.hh @@ -0,0 +1,81 @@ +/* + * Copyright (C) Peter Gaal + * this code is derived from work of W.L. Chuang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _JPEG_FRAME_PARSER_HH_INCLUDED +#define _JPEG_FRAME_PARSER_HH_INCLUDED + + +class JpegFrameParser +{ +public: + JpegFrameParser(); + virtual ~JpegFrameParser(); + + unsigned char width() { return _width; } + unsigned char height() { return _height; } + unsigned char type() { return _type; } + unsigned char precision() { return _precision; } + unsigned char qFactor() { return _qFactor; } + + unsigned short restartInterval() { return _restartInterval; } + + unsigned char const* quantizationTables(unsigned short& length) + { + length = _qTablesLength; + return _qTables; + } + + int parse(unsigned char* data, unsigned int size); + + unsigned char const* scandata(unsigned int& length) + { + length = _scandataLength; + + return _scandata; + } + +private: + unsigned int scanJpegMarker(const unsigned char* data, + unsigned int size, + unsigned int* offset); + int readSOF(const unsigned char* data, + unsigned int size, unsigned int* offset); + unsigned int readDQT(const unsigned char* data, + unsigned int size, unsigned int offset); + int readDRI(const unsigned char* data, + unsigned int size, unsigned int* offset); + +private: + unsigned char _width; + unsigned char _height; + unsigned char _type; + unsigned char _precision; + unsigned char _qFactor; + + unsigned char* _qTables; + unsigned short _qTablesLength; + + unsigned short _restartInterval; + + unsigned char* _scandata; + unsigned int _scandataLength; +}; + + +#endif /* _JPEG_FRAME_PARSER_HH_INCLUDED */ diff --git a/src/modules/rtsp/LiveServerMediaSubsession.cpp b/src/modules/rtsp/LiveServerMediaSubsession.cpp new file mode 100755 index 0000000..9f69a05 --- /dev/null +++ b/src/modules/rtsp/LiveServerMediaSubsession.cpp @@ -0,0 +1,75 @@ +/** + * @file LiveServerMediaSubsession.cpp + * @author 吴晨 + * @brief 适配 live555 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#include "LiveServerMediaSubsession.hh" + +#include "liveMedia.hh" + +#include "BaseDeviceSource.hh" +#include "H26XDeviceSource.hh" +#include "MJPEGDeviceSource.hh" + +LiveServerMediaSubsession *LiveServerMediaSubsession::createNew(UsageEnvironment &env, codec_type_t codecType) { + return new LiveServerMediaSubsession(env, codecType); +} + +LiveServerMediaSubsession::LiveServerMediaSubsession(UsageEnvironment &env, codec_type_t codecType) + : OnDemandServerMediaSubsession(env, True), fCodecType(codecType), fDeviceSource(NULL) {} + +LiveServerMediaSubsession::~LiveServerMediaSubsession() {} + +FramedSource *LiveServerMediaSubsession::createNewStreamSource(unsigned clientSessionId, unsigned &estBitrate) { + if (fCodecType == ROV_H264) { + estBitrate = 3000; // kbps, estimate + H26XDeviceSource *streamSource = H26XDeviceSource::createNew(envir(), False); + fDeviceSource = streamSource; + return H264VideoStreamDiscreteFramer::createNew(envir(), streamSource, True); + } else if (fCodecType == ROV_H265) { + estBitrate = 2500; + H26XDeviceSource *streamSource = H26XDeviceSource::createNew(envir(), True); + fDeviceSource = streamSource; + return H265VideoStreamDiscreteFramer::createNew(envir(), streamSource, True); + } else if (fCodecType == ROV_JPEG) { + estBitrate = 8000; + MJPEGDeviceSource *streamSource = MJPEGDeviceSource::createNew(envir()); + fDeviceSource = streamSource; + return streamSource; + } else if (fCodecType == ROV_AAC) { + estBitrate = 64; + return NULL; + } else if (fCodecType == ROV_G711A || fCodecType == ROV_G711U) { + estBitrate = 128; + return NULL; + } + return NULL; +} + +RTPSink *LiveServerMediaSubsession::createNewRTPSink(Groupsock *rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource *inputSource) { + if (fCodecType == ROV_H264) { + OutPacketBuffer::increaseMaxSizeTo(512 * 1024); + return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (fCodecType == ROV_H265) { + OutPacketBuffer::increaseMaxSizeTo(512 * 1024); + return H265VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (fCodecType == ROV_JPEG) { + OutPacketBuffer::increaseMaxSizeTo(2 * 512 * 1024); + return JPEGVideoRTPSink::createNew(envir(), rtpGroupsock); + } else if (fCodecType == ROV_AAC) { + return SimpleRTPSink::createNew(envir(), rtpGroupsock, 0, 8000, "audio", "AAC", 1, False); + } else if (fCodecType == ROV_G711A) { + return SimpleRTPSink::createNew(envir(), rtpGroupsock, 0, 8000, "audio", "PCMA", 1, False); + } else if (fCodecType == ROV_G711U) { + return SimpleRTPSink::createNew(envir(), rtpGroupsock, 0, 8000, "audio", "PCMU", 1, False); + } + return NULL; +} + +void LiveServerMediaSubsession::closeStreamSource(FramedSource *inputSource) { + fDeviceSource = NULL; + OnDemandServerMediaSubsession::closeStreamSource(inputSource); +} diff --git a/src/modules/rtsp/LiveServerMediaSubsession.hh b/src/modules/rtsp/LiveServerMediaSubsession.hh new file mode 100755 index 0000000..a75ca5e --- /dev/null +++ b/src/modules/rtsp/LiveServerMediaSubsession.hh @@ -0,0 +1,32 @@ +/** + * @file LiveServerMediaSubsession.hh + * @author 吴晨 + * @brief 适配 live555 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#ifndef _LIVE_SERVER_MEDIA_SUBSESSION_HH +#define _LIVE_SERVER_MEDIA_SUBSESSION_HH + +#include "OnDemandServerMediaSubsession.hh" + +#include "BaseDeviceSource.hh" + +class LiveServerMediaSubsession : public OnDemandServerMediaSubsession { +public: + static LiveServerMediaSubsession *createNew(UsageEnvironment &env, codec_type_t codecType); + BaseDeviceSource *deviceSource() { return fDeviceSource; } + +protected: + LiveServerMediaSubsession(UsageEnvironment &env, codec_type_t codecType); + virtual ~LiveServerMediaSubsession(); + virtual FramedSource *createNewStreamSource(unsigned clientSessionId, unsigned &estBitrate); + virtual RTPSink *createNewRTPSink(Groupsock *rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource *inputSource); + virtual void closeStreamSource(FramedSource* inputSource); + +private: + codec_type_t fCodecType; + BaseDeviceSource *fDeviceSource; +}; +#endif diff --git a/src/modules/rtsp/MJPEGDeviceSource.cpp b/src/modules/rtsp/MJPEGDeviceSource.cpp new file mode 100755 index 0000000..5e16423 --- /dev/null +++ b/src/modules/rtsp/MJPEGDeviceSource.cpp @@ -0,0 +1,95 @@ +/** + * @file MJPEGDeviceSource.cpp + * @author 吴晨 + * @brief 适配 live555 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#include "MJPEGDeviceSource.hh" + +#include +#include +#include "GroupsockHelper.hh" + +unsigned MJPEGDeviceSource::referenceCount = 0; +EventTriggerId MJPEGDeviceSource::eventTriggerId = 0; + +MJPEGDeviceSource *MJPEGDeviceSource::createNew(UsageEnvironment &env) { + return new MJPEGDeviceSource(env); +} + +MJPEGDeviceSource::MJPEGDeviceSource(UsageEnvironment &env) + : JPEGVideoSource(env), parser(), frameDataQueue(3) { + if (eventTriggerId == 0) { + eventTriggerId = envir().taskScheduler().createEventTrigger([](void *clientData) { + ((MJPEGDeviceSource *) clientData)->deliverFrame(); + }); + } + ++referenceCount; +} + +MJPEGDeviceSource::~MJPEGDeviceSource() { + --referenceCount; + if (referenceCount == 0) { + envir().taskScheduler().deleteEventTrigger(eventTriggerId); + eventTriggerId = 0; + } + FrameData frameData; + while (frameDataQueue.try_dequeue(frameData)) { + delete[] frameData.data; + } +} + +codec_type_t MJPEGDeviceSource::getCodecType() { + return ROV_JPEG; +} + +void MJPEGDeviceSource::signalNewFrame(const FrameData &frameData) { + if (!frameDataQueue.try_enqueue(frameData)) { + envir() << "Stream data queue full!\n"; + delete[] frameData.data; + } + envir().taskScheduler().triggerEvent(eventTriggerId, this); +} + +unsigned MJPEGDeviceSource::maxFrameSize() const { + return 0; +} + +void MJPEGDeviceSource::doGetNextFrame() { + if (frameDataQueue.size_approx() > 0) { + deliverFrame(); + } +} + +void MJPEGDeviceSource::deliverFrame() { + if (!isCurrentlyAwaitingData()) return; + + FrameData buffer; + if (!frameDataQueue.try_dequeue(buffer)) return; + + if (parser.parse(buffer.data, buffer.length) != 0) { + delete[] buffer.data; + return; + } + + if (buffer.length > fMaxSize) { + fFrameSize = fMaxSize; + fNumTruncatedBytes = buffer.length - fMaxSize; + } else { + fFrameSize = buffer.length; + } + + if (buffer.timestamp == 0) { + gettimeofday(&fPresentationTime, NULL); + } else { + fPresentationTime.tv_sec = buffer.timestamp / 1000000; + fPresentationTime.tv_usec = buffer.timestamp % 1000000; + } + + memcpy(fTo, buffer.data, fFrameSize); + delete[] buffer.data; + + afterGetting(this); +} diff --git a/src/modules/rtsp/MJPEGDeviceSource.hh b/src/modules/rtsp/MJPEGDeviceSource.hh new file mode 100755 index 0000000..80ff2f2 --- /dev/null +++ b/src/modules/rtsp/MJPEGDeviceSource.hh @@ -0,0 +1,51 @@ +/** + * @file MJPEGDeviceSource.hh + * @author 吴晨 + * @brief 适配 live555 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#ifndef _MJPEG_DEVICE_SOURCE_HH +#define _MJPEG_DEVICE_SOURCE_HH + +#include "JPEGVideoSource.hh" +#include "readerwritercircularbuffer.h" + +#include "BaseDeviceSource.hh" +#include "JpegFrameParser.hh" + +class MJPEGDeviceSource : public JPEGVideoSource, public BaseDeviceSource { +public: + static MJPEGDeviceSource *createNew(UsageEnvironment &env); + +public: + virtual u_int8_t type() { return parser.type(); } + virtual u_int8_t qFactor() { return parser.qFactor(); } + virtual u_int8_t width() { return parser.width(); } + virtual u_int8_t height() { return parser.height(); } + virtual u_int8_t const *quantizationTables(u_int8_t &precision, u_int16_t &length) { + precision = parser.precision(); + return parser.quantizationTables(length); + } + virtual u_int16_t restartInterval() { return parser.restartInterval(); } + virtual codec_type_t getCodecType(); + virtual void signalNewFrame(const FrameData &frameData); + +protected: + MJPEGDeviceSource(UsageEnvironment &env); + virtual ~MJPEGDeviceSource(); + virtual unsigned maxFrameSize() const; + virtual void doGetNextFrame(); + +private: + void deliverFrame(); + +private: + JpegFrameParser parser; + moodycamel::BlockingReaderWriterCircularBuffer frameDataQueue; + static unsigned referenceCount; + static EventTriggerId eventTriggerId; +}; + +#endif diff --git a/src/modules/rtsp/build.mk b/src/modules/rtsp/build.mk new file mode 100755 index 0000000..beb5195 --- /dev/null +++ b/src/modules/rtsp/build.mk @@ -0,0 +1,24 @@ +# +# rtsp/build.mk +# rtsp 模块构建脚本 +# +# Copyright (c) 2022-2024 OurEDA +# + +TARGET := rtsp +CUR_INCS := -I$(CUR_SRC_DIR) \ + -I$(ROV_HOME)/configs \ + -I$(ROV_HOME)/common \ + -I$(ROV_HOME)/modules \ + -I$(ROV_HOME)/modules/tcp/generated \ + -I$(ROV_OUT_DIR)/include \ + -I$(ROV_OUT_DIR)/include/groupsock \ + -I$(ROV_OUT_DIR)/include/BasicUsageEnvironment \ + -I$(ROV_OUT_DIR)/include/UsageEnvironment \ + -I$(ROV_OUT_DIR)/include/liveMedia +CUR_SRCS := $(wildcard $(CUR_SRC_DIR)/*.c) \ + $(wildcard $(CUR_SRC_DIR)/*.cpp) +CUR_STATIC_LIBS := -lliveMedia -lUsageEnvironment -lBasicUsageEnvironment -lgroupsock +CUR_DEPS := live555 readerwriterqueue mpp + +$(call build_module,$(TARGET)) diff --git a/src/modules/rtsp/rtsp_server.cpp b/src/modules/rtsp/rtsp_server.cpp new file mode 100755 index 0000000..448eca1 --- /dev/null +++ b/src/modules/rtsp/rtsp_server.cpp @@ -0,0 +1,132 @@ +/** + * @file rtsp_server.cpp + * @author 吴晨 + * @brief 基于 live555 的 RTSP 服务器 + * + * @copyright Copyright (c) 2022-2024 OurEDA + */ + +#include +#include +#include "BasicUsageEnvironment.hh" +#include "GroupsockHelper.hh" +#include "liveMedia.hh" + +#include "module_init.h" +#include "platform/log.h" +#include "msgbus/ipc.h" +#include "LiveServerMediaSubsession.hh" + +static const char *stream_name = "main"; +static const char *description = "Session streamed by the main camera of rouring ROV"; + +static volatile char stop_running; +static pthread_t rtsp_thread; +static TaskScheduler *scheduler; +static UsageEnvironment *env; +static RTSPServer *rtspServer; +static LiveServerMediaSubsession *videoSubsession; +static LiveServerMediaSubsession *audioSubsession; + +static void announceURL(RTSPServer *rtspServer, ServerMediaSession *sms) { + if (rtspServer == NULL || sms == NULL) return; // sanuty check + UsageEnvironment &env = rtspServer->envir(); + env << "Play this stream using the URL "; + if (weHaveAnIPv4Address(env)) { + char *url = rtspServer->ipv4rtspURL(sms); + env << "\"" << url << "\""; + delete[] url; + if (weHaveAnIPv6Address(env)) env << " or "; + } + if (weHaveAnIPv6Address(env)) { + char *url = rtspServer->ipv6rtspURL(sms); + env << "\"" << url << "\""; + delete[] url; + } + env << "\n"; +} + +static void *rtsp_loop(void *arg) { + UNUSED(arg); + env->taskScheduler().doEventLoop(&stop_running); + return nullptr; +} + +static void on_msg_received(ipc_request_t *req) { + if (req->id == IPC_MSG_ID_RTSP_STREAM_VIDEO) { + const ipc_msg_rtsp_stream_t *packet = (const ipc_msg_rtsp_stream_t *)req->msg; + if (videoSubsession == nullptr || videoSubsession->deviceSource() == nullptr) { + delete[] packet->data; + ipc_reply(req, nullptr); + return; + } + videoSubsession->deviceSource()->signalNewFrame(*packet); + ipc_reply(req, nullptr); + } +} + +static bool rtsp_start() { + LOG_DEBUG("Starting RTSP server...\n"); + ipc_register_callback(MOD_RTSP, on_msg_received); + + scheduler = BasicTaskScheduler::createNew(); + env = BasicUsageEnvironment::createNew(*scheduler); + rtspServer = RTSPServer::createNew(*env, 554, nullptr, 10U); + if (rtspServer == nullptr) { + *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n"; + return false; + } + + ipc_msg_mpp_stream_info_req stream_info_req; + stream_info_req.num = 0; + ipc_request_t req = IPC_REQUEST_INIT(MOD_MPP); + req.id = IPC_MSG_ID_MPP_STREAM_INFO; + req.msg = (const ipc_msg_t *)&stream_info_req; + req.length = sizeof(ipc_msg_mpp_stream_info_req); + int result = ipc_send(&req); + if (result != IPC_OK) { + *env << "Failed to get live stream params\n"; + return false; + } + ipc_msg_mpp_stream_info_res *stream_info_res = &req.res->msg.mpp_stream_info_res; + if (stream_info_res->video_codec < ROV_AUDIO) { + videoSubsession = LiveServerMediaSubsession::createNew(*env, stream_info_res->video_codec); + } + if (stream_info_res->audio_codec > ROV_AUDIO) { + audioSubsession = LiveServerMediaSubsession::createNew(*env, stream_info_res->audio_codec); + } + free(req.res); + + ServerMediaSession *sms = ServerMediaSession::createNew(*env, stream_name, stream_name, description); + if (videoSubsession != nullptr) { + sms->addSubsession(videoSubsession); + } + if (audioSubsession != nullptr) { + sms->addSubsession(audioSubsession); + } + rtspServer->addServerMediaSession(sms); + announceURL(rtspServer, sms); + + stop_running = 0; + result = pthread_create(&rtsp_thread, NULL, rtsp_loop, NULL); + if (result != 0) { + *env << "Create RTSP server thread failed, result: " << result << "\n"; + return false; + } + return true; +} + +static void rtsp_stop() { + stop_running = 1; + pthread_join(rtsp_thread, NULL); + videoSubsession = audioSubsession = nullptr; + // 若销毁RTSP服务端时客户端未断开连接则不会关闭连接, 所以在销毁之前关闭一下 + rtspServer->closeAllClientSessionsForServerMediaSession(stream_name); + Medium::close(rtspServer); + if (env && !env->reclaim()) { + fprintf(stderr, "!!! UsageEnvironment release failed !!!\n"); + } + delete scheduler; +} + +MODULE_RUN(rtsp_start, rtsp_stop, 09); diff --git a/xmake.lua b/xmake.lua index 2c706d8..a5200f7 100755 --- a/xmake.lua +++ b/xmake.lua @@ -19,32 +19,43 @@ add_requires("live555 2024.11.28") add_requires("zlog 1.2.17", { system = false }) target("ISP") -set_kind("static") -add_files("src/isp/*.c") -add_includedirs("src") -add_deps("sample_common") -add_packages("zlog") + set_kind("static") + add_files("src/modules/isp/*.c") + add_includedirs("src") + add_deps("sample_common") + add_packages("zlog") +target_end() target("NNIE") -set_kind("static") -add_files("src/nnie/*.c") -add_includedirs("src") -add_deps("sample_common", "sample_svp") -add_packages("zlog") + set_kind("static") + add_files("src/modules/nnie/*.c") + add_includedirs("src") + add_deps("sample_common", "sample_svp") + add_packages("zlog") +target_end() + +target("RTSP") + set_kind("static") + add_files("src/modules/rtsp/*.cpp") + add_includedirs("src") + add_packages("live555", "zlog") + add_links("pthread") +target_end() target("CatFeeder") -set_kind("binary") -add_includedirs("src") -add_files("src/*.cpp") -add_deps("hi_library", "ISP", "NNIE") -add_packages("zlog") -add_links("stdc++fs") -after_build(function(target) - os.cp("src/log.conf", "$(buildir)/cross/arm/release") -end) + set_kind("binary") + add_includedirs("src") + add_files("src/*.cpp") + add_deps("hi_library", "ISP", "NNIE", "RTSP") + add_packages("zlog") + add_links("stdc++fs") + after_build(function(target) + os.cp("src/log.conf", "$(buildir)/cross/arm/release") + end) -if is_mode("debug") then - add_defines("DEBUG") - set_symbols("debug") - set_optimize("none") -end + if is_mode("debug") then + add_defines("DEBUG") + set_symbols("debug") + set_optimize("none") + end +target_end()