From cb0579a16d2ca020476047f8351ce01a313ee7f8 Mon Sep 17 00:00:00 2001 From: XiaoYan Lin Date: Sun, 2 Jul 2023 12:02:33 +0800 Subject: [PATCH 01/92] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81hls-f?= =?UTF-8?q?mp4=E7=9B=B4=E6=92=AD(#2603=20#977=20#1965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同时主要优化点包括: 1、编译宏特性开关优化。 2、转协议复用器相关创建代码移动至Recorder类。 3、转协议复用器onAllTrackReady函数修改为addTrackCompleted。 4、startRecord/stopRecord/isRecording接口新增支持ts/fmp4/hls-fmp4协议。 Co-authored-by: xia-chu <771730766@qq.com> Co-authored-by: linxiaoyan87 --- 3rdpart/CMakeLists.txt | 50 ++++---- CMakeLists.txt | 1 + conf/config.ini | 8 +- src/Common/MediaSource.cpp | 2 + src/Common/MediaSource.h | 5 +- src/Common/MultiMediaSourceMuxer.cpp | 165 +++++++++++++++++---------- src/Common/MultiMediaSourceMuxer.h | 6 +- src/Common/config.cpp | 2 + src/Common/config.h | 4 +- src/Common/macros.h | 1 + src/FMP4/FMP4MediaSourceMuxer.h | 6 +- src/Http/HttpFileManager.cpp | 16 ++- src/Record/HlsMaker.cpp | 82 +++++++------ src/Record/HlsMaker.h | 31 ++++- src/Record/HlsMakerImp.cpp | 46 ++++---- src/Record/HlsMakerImp.h | 12 +- src/Record/HlsMediaSource.h | 2 +- src/Record/HlsRecorder.h | 72 +++++++++--- src/Record/MP4.cpp | 5 +- src/Record/MP4.h | 4 +- src/Record/MP4Muxer.cpp | 18 +-- src/Record/MP4Muxer.h | 36 +++++- src/Record/MPEG.h | 4 +- src/Record/Recorder.cpp | 45 +++++++- src/Record/Recorder.h | 8 +- src/Rtmp/RtmpMediaSourceMuxer.h | 3 +- src/Rtsp/RtspMediaSourceMuxer.h | 3 +- 27 files changed, 413 insertions(+), 224 deletions(-) diff --git a/3rdpart/CMakeLists.txt b/3rdpart/CMakeLists.txt index 07d5d388..c90129d3 100644 --- a/3rdpart/CMakeLists.txt +++ b/3rdpart/CMakeLists.txt @@ -47,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") # TODO: 补一个函数处理各种库 # 添加 mov、flv 库用于 MP4 录制 -if(ENABLE_MP4) - message(STATUS "ENABLE_MP4 defined") - +if (ENABLE_MP4 OR ENABLE_HLS_FMP4) # MOV set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) - aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) + aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) add_library(mov STATIC ${MOV_SRC_LIST}) add_library(MediaServer::mov ALIAS mov) - target_compile_definitions(mov - PUBLIC -DENABLE_MP4) - target_compile_options(mov - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(mov - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") # FLV set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv) aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST) - aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) + aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) add_library(flv STATIC ${FLV_SRC_LIST}) add_library(MediaServer::flv ALIAS flv) - target_compile_options(flv - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(flv - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") - update_cached_list(MK_LINK_LIBRARIES - MediaServer::flv MediaServer::mov) - update_cached_list(MK_COMPILE_DEFINITIONS - ENABLE_MP4) -endif() + update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov) + + if (ENABLE_MP4) + message(STATUS "ENABLE_MP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MP4) + endif () + if (ENABLE_HLS_FMP4) + message(STATUS "ENABLE_HLS_FMP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS_FMP4) + endif () +endif () # 添加 mpeg 用于支持 ts 生成 if(ENABLE_RTPPROXY OR ENABLE_HLS) @@ -108,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) if(ENABLE_RTPPROXY) + message(STATUS "ENABLE_RTPPROXY defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) endif() if(ENABLE_HLS) + message(STATUS "ENABLE_HLS defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) endif() endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 66689d9c..780adb4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON) option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF) option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF) option(ENABLE_MP4 "Enable MP4" ON) +option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON) option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON) option(ENABLE_MYSQL "Enable MySQL" OFF) option(ENABLE_OPENSSL "Enable OpenSSL" ON) diff --git a/conf/config.ini b/conf/config.ini index bd72cac6..105a76ad 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -45,8 +45,10 @@ add_mute_audio=1 #此参数不应大于播放器超时时间;单位毫秒 continue_push_ms=15000 -#是否开启转换为hls +#是否开启转换为hls(mpegts) enable_hls=1 +#是否开启转换为hls(fmp4) +enable_hls_fmp4=0 #是否开启MP4录制 enable_mp4=0 #是否开启转换为rtsp/webrtc @@ -124,7 +126,7 @@ segDur=2 segNum=3 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 -#是否广播 ts 切片完成通知 +#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts) broadcastRecordTs=0 #直播hls文件删除延时,单位秒,issue: #913 deleteDelaySec=10 @@ -150,7 +152,7 @@ on_play=https://127.0.0.1/index/hook/on_play on_publish=https://127.0.0.1/index/hook/on_publish #录制mp4切片完成事件 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 -# 录制 hls ts 切片完成事件 +# 录制 hls ts(或fmp4) 切片完成事件 on_record_ts=https://127.0.0.1/index/hook/on_record_ts #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 7c48c3e1..16adfd8d 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -59,6 +59,7 @@ ProtocolOption::ProtocolOption() { GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); + GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4); GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4); GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); @@ -83,6 +84,7 @@ ProtocolOption::ProtocolOption() { continue_push_ms = s_continue_push_ms; enable_hls = s_enable_hls; + enable_hls_fmp4 = s_enable_hls_fmp4; enable_mp4 = s_enable_mp4; enable_rtsp = s_enable_rtsp; enable_rtmp = s_enable_rtmp; diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 9391e6fb..3438e350 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -151,8 +151,10 @@ public: //断连续推延时,单位毫秒,默认采用配置文件 uint32_t continue_push_ms; - //是否开启转换为hls + //是否开启转换为hls(mpegts) bool enable_hls; + //是否开启转换为hls(fmp4) + bool enable_hls_fmp4; //是否开启MP4录制 bool enable_mp4; //是否开启转换为rtsp/webrtc @@ -194,6 +196,7 @@ public: GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(enable_hls); + GET_OPT_VALUE(enable_hls_fmp4); GET_OPT_VALUE(enable_mp4); GET_OPT_VALUE(enable_rtsp); GET_OPT_VALUE(enable_rtmp); diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index f1921a95..f70bc2a1 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -37,6 +37,7 @@ static std::shared_ptr makeRecorder(MediaSource &sender, con for (auto &track : tracks) { recorder->addTrack(track); } + recorder->addTrackCompleted(); return recorder; } @@ -97,17 +98,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_ if (option.enable_hls) { _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, _tuple, option)); } + if (option.enable_hls_fmp4) { + _hls_fmp4 = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option)); + } if (option.enable_mp4) { _mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option); } if (option.enable_ts) { - _ts = std::make_shared(_tuple, option); + _ts = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_ts, _tuple, option)); } -#if defined(ENABLE_MP4) if (option.enable_fmp4) { - _fmp4 = std::make_shared(_tuple, option); + _fmp4 = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option)); } -#endif //音频相关设置 enableAudio(option.enable_audio); @@ -128,14 +130,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptrsetListener(self); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->setListener(self); } -#endif - auto hls = _hls; - if (hls) { - hls->setListener(self); + if (_hls_fmp4) { + _hls_fmp4->setListener(self); + } + if (_hls) { + _hls->setListener(self); } } @@ -144,15 +146,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr &list } int MultiMediaSourceMuxer::totalReaderCount() const { - auto hls = _hls; return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) + - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->readerCount() : 0) + - #endif (_mp4 ? _option.mp4_as_player : 0) + - (hls ? hls->readerCount() : 0) + + (_hls ? _hls->readerCount() : 0) + + (_hls_fmp4 ? _hls_fmp4->readerCount() : 0) + (_ring ? _ring->readerCount() : 0); } @@ -180,6 +180,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { //此函数可能跨线程调用 bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { + CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller"); onceToken token(nullptr, [&]() { if (_option.mp4_as_player && type == Recorder::type_mp4) { //开启关闭mp4录制,触发观看人数变化相关事件 @@ -215,19 +216,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type } return true; } + case Recorder::type_hls_fmp4: { + if (start && !_hls_fmp4) { + //开始录制 + _option.hls_save_path = custom_path; + auto hls = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, _option)); + if (hls) { + //设置HlsMediaSource的事件监听器 + hls->setListener(shared_from_this()); + } + _hls_fmp4 = hls; + } else if (!start && _hls_fmp4) { + //停止录制 + _hls_fmp4 = nullptr; + } + return true; + } + case Recorder::type_fmp4: { + if (start && !_fmp4) { + auto fmp4 = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, _option)); + if (fmp4) { + fmp4->setListener(shared_from_this()); + } + _fmp4 = fmp4; + } else if (!start && _fmp4) { + _fmp4 = nullptr; + } + return true; + } + case Recorder::type_ts: { + if (start && !_ts) { + auto ts = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, _option)); + if (ts) { + ts->setListener(shared_from_this()); + } + _ts = ts; + } else if (!start && _ts) { + _ts = nullptr; + } + return true; + } default : return false; } } //此函数可能跨线程调用 bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) { - switch (type){ - case Recorder::type_hls : - return !!_hls; - case Recorder::type_mp4 : - return !!_mp4; - default: - return false; + switch (type) { + case Recorder::type_hls: return !!_hls; + case Recorder::type_mp4: return !!_mp4; + case Recorder::type_hls_fmp4: return !!_hls_fmp4; + case Recorder::type_fmp4: return !!_fmp4; + case Recorder::type_ts: return !!_ts; + default: return false; } } @@ -327,20 +368,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { if (_ts) { ret = _ts->addTrack(track) ? true : ret; } -#if defined(ENABLE_MP4) if (_fmp4) { ret = _fmp4->addTrack(track) ? true : ret; } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - ret = hls->addTrack(track) ? true : ret; + if (_hls) { + ret = _hls->addTrack(track) ? true : ret; } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->addTrack(track) ? true : ret; + if (_hls_fmp4) { + ret = _hls_fmp4->addTrack(track) ? true : ret; + } + if (_mp4) { + ret = _mp4->addTrack(track) ? true : ret; } return ret; } @@ -350,16 +388,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() { setMediaListener(getDelegate()); if (_rtmp) { - _rtmp->onAllTrackReady(); + _rtmp->addTrackCompleted(); } if (_rtsp) { - _rtsp->onAllTrackReady(); + _rtsp->addTrackCompleted(); + } + if (_ts) { + _ts->addTrackCompleted(); + } + if (_mp4) { + _mp4->addTrackCompleted(); } -#if defined(ENABLE_MP4) if (_fmp4) { - _fmp4->onAllTrackReady(); + _fmp4->addTrackCompleted(); } -#endif + if (_hls) { + _hls->addTrackCompleted(); + } + if (_hls_fmp4) { + _hls_fmp4->addTrackCompleted(); + } + auto listener = _track_listener.lock(); if (listener) { listener->onAllTrackReady(); @@ -407,21 +456,17 @@ void MultiMediaSourceMuxer::resetTracks() { if (_ts) { _ts->resetTracks(); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->resetTracks(); } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - hls->resetTracks(); + if (_hls_fmp4) { + _hls_fmp4->resetTracks(); } - - auto mp4 = _mp4; - if (mp4) { - mp4->resetTracks(); + if (_hls) { + _hls->resetTracks(); + } + if (_mp4) { + _mp4->resetTracks(); } } @@ -443,23 +488,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { ret = _ts->inputFrame(frame) ? true : ret; } - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - //此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 - auto hls = _hls; - if (hls) { - ret = hls->inputFrame(frame) ? true : ret; - } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->inputFrame(frame) ? true : ret; + if (_hls) { + ret = _hls->inputFrame(frame) ? true : ret; } -#if defined(ENABLE_MP4) + if (_hls_fmp4) { + ret = _hls_fmp4->inputFrame(frame) ? true : ret; + } + + if (_mp4) { + ret = _mp4->inputFrame(frame) ? true : ret; + } if (_fmp4) { ret = _fmp4->inputFrame(frame) ? true : ret; } -#endif - if (_ring) { if (frame->getTrackType() == TrackVideo) { // 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处 @@ -481,15 +523,14 @@ bool MultiMediaSourceMuxer::isEnabled(){ if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) { //无人观看时,每次检查是否真的无人观看 //有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能) - auto hls = _hls; _is_enable = (_rtmp ? _rtmp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) || - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->isEnabled() : false) || - #endif (_ring ? (bool)_ring->readerCount() : false) || - (hls ? hls->isEnabled() : false) || _mp4; + (_hls ? _hls->isEnabled() : false) || + (_hls_fmp4 ? _hls_fmp4->isEnabled() : false) || + _mp4; if (_is_enable) { //无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index a9983550..22b53c75 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -164,18 +164,14 @@ private: toolkit::Ticker _last_check; Stamp _stamp[2]; std::weak_ptr _track_listener; -#if defined(ENABLE_RTPPROXY) std::unordered_map _rtp_sender; -#endif //ENABLE_RTPPROXY - -#if defined(ENABLE_MP4) FMP4MediaSourceMuxer::Ptr _fmp4; -#endif RtmpMediaSourceMuxer::Ptr _rtmp; RtspMediaSourceMuxer::Ptr _rtsp; TSMediaSourceMuxer::Ptr _ts; MediaSinkInterface::Ptr _mp4; HlsRecorder::Ptr _hls; + HlsFMP4Recorder::Ptr _hls_fmp4; toolkit::EventPoller::Ptr _poller; RingType::Ptr _ring; diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 3d79d894..2e6b9421 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -102,6 +102,7 @@ const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kEnableHls = PROTOCOL_FIELD "enable_hls"; +const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4"; const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; @@ -127,6 +128,7 @@ static onceToken token([]() { mINI::Instance()[kContinuePushMS] = 15000; mINI::Instance()[kEnableHls] = 1; + mINI::Instance()[kEnableHlsFmp4] = 0; mINI::Instance()[kEnableMP4] = 0; mINI::Instance()[kEnableRtsp] = 1; mINI::Instance()[kEnableRtmp] = 1; diff --git a/src/Common/config.h b/src/Common/config.h index fb998cd5..9f043ecb 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -193,8 +193,10 @@ extern const std::string kAddMuteAudio; //断连续推延时,单位毫秒,默认采用配置文件 extern const std::string kContinuePushMS; -//是否开启转换为hls +//是否开启转换为hls(mpegts) extern const std::string kEnableHls; +//是否开启转换为hls(fmp4) +extern const std::string kEnableHlsFmp4; //是否开启MP4录制 extern const std::string kEnableMP4; //是否开启转换为rtsp/webrtc diff --git a/src/Common/macros.h b/src/Common/macros.h index dd9ad64d..4c61d11c 100644 --- a/src/Common/macros.h +++ b/src/Common/macros.h @@ -67,6 +67,7 @@ #define HLS_SCHEMA "hls" #define TS_SCHEMA "ts" #define FMP4_SCHEMA "fmp4" +#define HLS_FMP4_SCHEMA "hls.fmp4" #define SRT_SCHEMA "srt" #define DEFAULT_VHOST "__defaultVhost__" diff --git a/src/FMP4/FMP4MediaSourceMuxer.h b/src/FMP4/FMP4MediaSourceMuxer.h index 47ea5fd0..314951c9 100644 --- a/src/FMP4/FMP4MediaSourceMuxer.h +++ b/src/FMP4/FMP4MediaSourceMuxer.h @@ -11,8 +11,6 @@ #ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H #define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H -#if defined(ENABLE_MP4) - #include "FMP4MediaSource.h" #include "Record/MP4Muxer.h" @@ -63,7 +61,8 @@ public: return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true; } - void onAllTrackReady() { + void addTrackCompleted() override { + MP4MuxerMemory::addTrackCompleted(); _media_src->setInitSegment(getInitSegment()); } @@ -86,5 +85,4 @@ private: }//namespace mediakit -#endif// defined(ENABLE_MP4) #endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 7f69297b..3b0fa544 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -33,6 +33,7 @@ namespace mediakit { static int kHlsCookieSecond = 60; static const string kCookieName = "ZL_COOKIE"; static const string kHlsSuffix = "/hls.m3u8"; +static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8"; struct HttpCookieAttachment { //是否已经查找到过MediaSource @@ -278,7 +279,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo HttpCookieManager::Instance().delCookie(cookie); } - bool is_hls = media_info.schema == HLS_SCHEMA; + bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA; SockInfoImp::Ptr info = std::make_shared(); info->_identifier = sender.getIdentifier(); @@ -355,7 +356,7 @@ static string pathCat(const string &a, const string &b){ * @param cb 回调对象 */ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { - bool is_hls = end_with(file_path, kHlsSuffix); + bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix); if (!is_hls && !File::fileExist(file_path.data())) { //文件不存在且不是hls,那么直接返回404 sendNotFound(cb); @@ -363,8 +364,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m } if (is_hls) { // hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS - const_cast(media_info.schema) = HLS_SCHEMA; - replace(const_cast(media_info.stream), kHlsSuffix, ""); + if (end_with(file_path, kHlsSuffix)) { + const_cast(media_info.schema) = HLS_SCHEMA; + replace(const_cast(media_info.stream), kHlsSuffix, ""); + } else { + const_cast(media_info.schema) = HLS_FMP4_SCHEMA; + replace(const_cast(media_info.stream), kHlsFMP4Suffix, ""); + } } weak_ptr weakSession = static_pointer_cast(sender.shared_from_this()); @@ -621,4 +627,4 @@ HttpResponseInvokerImp::operator bool(){ } -}//namespace mediakit \ No newline at end of file +}//namespace mediakit diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index bc6cf53d..31f705b8 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -8,6 +8,7 @@ * may be found in the AUTHORS file in the root of the source tree. */ +#include #include "HlsMaker.h" #include "Common/config.h" @@ -15,83 +16,76 @@ using namespace std; namespace mediakit { -HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { +HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { + _is_fmp4 = is_fmp4; //最小允许设置为0,0个切片代表点播 _seg_number = seg_number; _seg_duration = seg_duration; _seg_keep = seg_keep; } -HlsMaker::~HlsMaker() = default; - void HlsMaker::makeIndexFile(bool eof) { - char file_content[1024]; int maxSegmentDuration = 0; - for (auto &tp : _seg_dur_list) { int dur = std::get<0>(tp); if (dur > maxSegmentDuration) { maxSegmentDuration = dur; } } + auto index_seq = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + string index_str; + index_str.reserve(2048); + index_str += "#EXTM3U\n"; + index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n"); + if (_seg_number == 0) { + index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n"; } else { - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-VERSION:3\n" - "#EXT-X-ALLOW-CACHE:NO\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + index_str += "#EXT-X-ALLOW-CACHE:NO\n"; + } + index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n"; + index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n"; + if (_is_fmp4) { + index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n"; } - - m3u8.assign(file_content); + stringstream ss; for (auto &tp : _seg_dur_list) { - snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data()); - m3u8.append(file_content); + ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n"; } + index_str += ss.str(); if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); + index_str += "#EXT-X-ENDLIST\n"; } - onWriteHls(m3u8); + onWriteHls(index_str); } +void HlsMaker::inputInitSegment(const char *data, size_t len) { + if (!_is_fmp4) { + throw std::invalid_argument("Only fmp4-hls can input init segment"); + } + onWriteInitSegment(data, len); +} -void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { +void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { if (data && len) { if (timestamp < _last_timestamp) { - //时间戳回退了,切片时长重新计时 - WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; + // 时间戳回退了,切片时长重新计时 + WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp; _last_seg_timestamp = _last_timestamp = timestamp; } if (is_idr_fast_packet) { - //尝试切片ts + // 尝试切片ts addNewSegment(timestamp); } if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); + // 存在切片才写入ts数据 + onWriteSegment(data, len); _last_timestamp = timestamp; } } else { - //resetTracks时触发此逻辑 + // resetTracks时触发此逻辑 flushLastSegment(false); } } @@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){ makeIndexFile(eof); } -bool HlsMaker::isLive() { +bool HlsMaker::isLive() const { return _seg_number != 0; } -bool HlsMaker::isKeep() { +bool HlsMaker::isKeep() const { return _seg_keep; } +bool HlsMaker::isFmp4() const { + return _is_fmp4; +} + void HlsMaker::clear() { _file_index = 0; _last_timestamp = 0; diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index be20ba1b..185a19d1 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -21,12 +21,13 @@ namespace mediakit { class HlsMaker { public: /** + * @param is_fmp4 使用fmp4还是mpegts * @param seg_duration 切片文件长度 * @param seg_number 切片个数 * @param seg_keep 是否保留切片文件 */ - HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); - virtual ~HlsMaker(); + HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); + virtual ~HlsMaker() = default; /** * 写入ts数据 @@ -35,17 +36,29 @@ public: * @param timestamp 毫秒时间戳 * @param is_idr_fast_packet 是否为关键帧第一个包 */ - void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + + /** + * 输入fmp4 init segment + * @param data 数据 + * @param len 数据长度 + */ + void inputInitSegment(const char *data, size_t len); /** * 是否为直播 */ - bool isLive(); + bool isLive() const; /** * 是否保留切片文件 */ - bool isKeep(); + bool isKeep() const; + + /** + * 是否采用fmp4切片还是mpegts + */ + bool isFmp4() const; /** * 清空记录 @@ -66,6 +79,13 @@ protected: */ virtual void onDelSegment(uint64_t index) = 0; + /** + * 写init.mp4切片文件回调 + * @param data + * @param len + */ + virtual void onWriteInitSegment(const char *data, size_t len) = 0; + /** * 写ts切片文件回调 * @param data @@ -109,6 +129,7 @@ private: void addNewSegment(uint64_t timestamp); private: + bool _is_fmp4 = false; float _seg_duration = 0; uint32_t _seg_number = 0; bool _seg_keep = false; diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index 4a140ac6..bbc49df5 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -21,21 +21,14 @@ using namespace toolkit; namespace mediakit { -HlsMakerImp::HlsMakerImp(const string &m3u8_file, - const string ¶ms, - uint32_t bufSize, - float seg_duration, - uint32_t seg_number, - bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) { +HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string ¶ms, uint32_t bufSize, float seg_duration, + uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) { _poller = EventPollerPool::Instance().getPoller(); _path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/')); _path_hls = m3u8_file; _params = params; _buf_size = bufSize; - _file_buf.reset(new char[bufSize], [](char *ptr) { - delete[] ptr; - }); - + _file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; }); _info.folder = _path_prefix; } @@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() { } void HlsMakerImp::clearCache(bool immediately, bool eof) { - //录制完了 + // 录制完了 flushLastSegment(eof); - if (!isLive()||isKeep()) { + if (!isLive() || isKeep()) { return; } @@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) { _file = nullptr; _segment_file_paths.clear(); - //hls直播才删除文件 + // hls直播才删除文件 GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); if (!delay || immediately) { File::delete_file(_path_prefix.data()); @@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { auto strDate = getTimeStr("%Y-%m-%d"); auto strHour = getTimeStr("%H"); auto strTime = getTimeStr("%M-%S"); - segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts"; + segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << (isFmp4() ? ".mp4" : ".ts"); segment_path = _path_prefix + "/" + segment_name; if (isLive()) { _segment_file_paths.emplace(index, segment_path); @@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { } _file = makeFile(segment_path, true); - //保存本切片的元数据 + // 保存本切片的元数据 _info.start_time = ::time(NULL); _info.file_name = segment_name; _info.file_path = segment_path; _info.url = _info.app + "/" + _info.stream + "/" + segment_name; if (!_file) { - WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); + WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg(); } if (_params.empty()) { return segment_name; @@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) { _segment_file_paths.erase(it); } +void HlsMakerImp::onWriteInitSegment(const char *data, size_t len) { + string init_seg_path = _path_prefix + "/init.mp4"; + _file = makeFile(init_seg_path, true); + + if (_file) { + fwrite(data, len, 1, _file.get()); + _file = nullptr; + } else { + WarnL << "Create file failed," << init_seg_path << " " << get_uv_errmsg(); + } +} + void HlsMakerImp::onWriteSegment(const char *data, size_t len) { if (_file) { fwrite(data, len, 1, _file.get()); @@ -132,13 +137,12 @@ void HlsMakerImp::onWriteHls(const std::string &data) { _media_src->setIndexFile(data); } } else { - WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); + WarnL << "Create hls file failed," << _path_hls << " " << get_uv_errmsg(); } - //DebugL << "\r\n" << string(data,len); } void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { - //关闭并flush文件到磁盘 + // 关闭并flush文件到磁盘 _file = nullptr; GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); @@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s _info.app = app; _info.stream = stream_id; _info.vhost = vhost; - _media_src = std::make_shared(_info); + _media_src = std::make_shared(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info); } HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { return _media_src; } -}//namespace mediakit \ No newline at end of file +} // namespace mediakit \ No newline at end of file diff --git a/src/Record/HlsMakerImp.h b/src/Record/HlsMakerImp.h index 6b9acffa..07bef1d6 100644 --- a/src/Record/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -19,15 +19,10 @@ namespace mediakit { -class HlsMakerImp : public HlsMaker{ +class HlsMakerImp : public HlsMaker { public: - HlsMakerImp(const std::string &m3u8_file, - const std::string ¶ms, - uint32_t bufSize = 64 * 1024, - float seg_duration = 5, - uint32_t seg_number = 3, - bool seg_keep = false); - + HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, uint32_t bufSize = 64 * 1024, + float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); ~HlsMakerImp() override; /** @@ -52,6 +47,7 @@ public: protected: std::string onOpenSegment(uint64_t index) override ; void onDelSegment(uint64_t index) override; + void onWriteInitSegment(const char *data, size_t len) override; void onWriteSegment(const char *data, size_t len) override; void onWriteHls(const std::string &data) override; void onFlushLastSegment(uint64_t duration_ms) override; diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h index a1149a7f..94cfdf4d 100644 --- a/src/Record/HlsMediaSource.h +++ b/src/Record/HlsMediaSource.h @@ -25,7 +25,7 @@ public: using RingType = toolkit::RingBuffer; using Ptr = std::shared_ptr; - HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {} + HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {} ~HlsMediaSource() override = default; /** diff --git a/src/Record/HlsRecorder.h b/src/Record/HlsRecorder.h index 1ec13f86..aa1a4960 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -13,27 +13,27 @@ #include "HlsMakerImp.h" #include "MPEG.h" +#include "MP4Muxer.h" #include "Common/config.h" namespace mediakit { -class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this { +template +class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this > { public: - using Ptr = std::shared_ptr; - - HlsRecorder(const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) : MpegMuxer(false) { + HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) { GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum); GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); _option = option; - _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); - //清空上次的残余文件 + _hls = std::make_shared(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + // 清空上次的残余文件 _hls->clearCache(); } - ~HlsRecorder() { MpegMuxer::flush(); }; + ~HlsRecorderBase() override = default; void setMediaSource(const MediaTuple& tuple) { _hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream); @@ -41,7 +41,7 @@ public: void setListener(const std::weak_ptr &listener) { setDelegate(listener); - _hls->getMediaSource()->setListener(shared_from_this()); + _hls->getMediaSource()->setListener(this->shared_from_this()); } int readerCount() { return _hls->getMediaSource()->readerCount(); } @@ -64,7 +64,7 @@ public: _hls->getMediaSource()->setIndexFile(""); } if (_enabled || !_option.hls_demand) { - return MpegMuxer::inputFrame(frame); + return Muxer::inputFrame(frame); } return false; } @@ -74,20 +74,54 @@ public: return _option.hls_demand ? (_clear_cache ? true : _enabled) : true; } -private: - void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { - if (!buffer) { - _hls->inputData(nullptr, 0, timestamp, key_pos); - } else { - _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); - } - } - -private: +protected: bool _enabled = true; bool _clear_cache = false; ProtocolOption _option; std::shared_ptr _hls; }; + +class HlsRecorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsRecorder(ARGS && ...args) : HlsRecorderBase(false, std::forward(args)...) {} + ~HlsRecorder() override { this->flush(); } + +private: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (!buffer) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); + } + } +}; + +class HlsFMP4Recorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase(true, std::forward(args)...) {} + ~HlsFMP4Recorder() override { this->flush(); } + + void addTrackCompleted() override { + HlsRecorderBase::addTrackCompleted(); + auto data = getInitSegment(); + _hls->inputInitSegment(data.data(), data.size()); + } + +private: + void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override { + if (buffer.empty()) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos); + } + } +}; + }//namespace mediakit #endif //HLSRECORDER_H diff --git a/src/Record/MP4.cpp b/src/Record/MP4.cpp index 8d92c256..10f1b0ee 100644 --- a/src/Record/MP4.cpp +++ b/src/Record/MP4.cpp @@ -8,7 +8,8 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) + #include "MP4.h" #include "Util/File.h" #include "Util/logger.h" @@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){ } }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) diff --git a/src/Record/MP4.h b/src/Record/MP4.h index 14bc1ca7..63e7af9c 100644 --- a/src/Record/MP4.h +++ b/src/Record/MP4.h @@ -11,7 +11,7 @@ #ifndef ZLMEDIAKIT_MP4_H #define ZLMEDIAKIT_MP4_H -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include #include @@ -136,5 +136,5 @@ private: }; }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #endif //ZLMEDIAKIT_MP4_H diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index cde87b2c..69db4237 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -8,7 +8,7 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include "MP4Muxer.h" #include "Extension/AAC.h" @@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) { return false; } - bool key_frame = frame->keyFrame(); - - //flush切片 + // flush切片 saveSegment(); auto data = _memory_file->getAndClearMemory(); if (!data.empty()) { - //输出切片数据 - onSegmentData(std::move(data), frame->dts(), _key_frame); + // 输出切片数据 + onSegmentData(std::move(data), _last_dst, _key_frame); _key_frame = false; } - if (key_frame) { + if (frame->keyFrame()) { _key_frame = true; } - + if (frame->getTrackType() == TrackVideo || !haveVideo()) { + _last_dst = frame->dts(); + } return MP4MuxerInterface::inputFrame(frame); } }//namespace mediakit -#endif//#ifdef ENABLE_MP4 +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) diff --git a/src/Record/MP4Muxer.h b/src/Record/MP4Muxer.h index 83b939f6..3b48ad7c 100644 --- a/src/Record/MP4Muxer.h +++ b/src/Record/MP4Muxer.h @@ -11,13 +11,13 @@ #ifndef ZLMEDIAKIT_MP4MUXER_H #define ZLMEDIAKIT_MP4MUXER_H -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include "Common/MediaSink.h" #include "Common/Stamp.h" #include "MP4.h" -namespace mediakit{ +namespace mediakit { class MP4MuxerInterface : public MediaSinkInterface { public: @@ -147,11 +147,39 @@ protected: private: bool _key_frame = false; + uint64_t _last_dst = 0; std::string _init_segment; MP4FileMemory::Ptr _memory_file; }; +} // namespace mediakit -}//namespace mediakit -#endif//#ifdef ENABLE_MP4 +#else + +#include "Common/MediaSink.h" + +namespace mediakit { + +class MP4MuxerMemory : public MediaSinkInterface { +public: + MP4MuxerMemory() = default; + ~MP4MuxerMemory() override = default; + + bool addTrack(const Track::Ptr & track) override { return false; } + bool inputFrame(const Frame::Ptr &frame) override { return false; } + const std::string &getInitSegment() { static std::string kNull; return kNull; }; + +protected: + /** + * 输出fmp4切片回调函数 + * @param std::string 切片内容 + * @param stamp 切片末尾时间戳 + * @param key_frame 是否有关键帧 + */ + virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0; +}; + +} // namespace mediakit + +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #endif //ZLMEDIAKIT_MP4MUXER_H diff --git a/src/Record/MPEG.h b/src/Record/MPEG.h index 1dd69d48..567d1b74 100644 --- a/src/Record/MPEG.h +++ b/src/Record/MPEG.h @@ -25,7 +25,7 @@ namespace mediakit { //该类用于产生MPEG-TS/MPEG-PS class MpegMuxer : public MediaSinkInterface { public: - MpegMuxer(bool is_ps); + MpegMuxer(bool is_ps = false); ~MpegMuxer() override; /** @@ -86,7 +86,7 @@ namespace mediakit { class MpegMuxer : public MediaSinkInterface { public: - MpegMuxer(bool is_ps) {} + MpegMuxer(bool is_ps = false) {} ~MpegMuxer() override = default; bool addTrack(const Track::Ptr &track) override { return false; } void resetTracks() override {} diff --git a/src/Record/Recorder.cpp b/src/Record/Recorder.cpp index d9cd75f7..6183ff1c 100644 --- a/src/Record/Recorder.cpp +++ b/src/Record/Recorder.cpp @@ -14,7 +14,8 @@ #include "Common/MediaSource.h" #include "MP4Recorder.h" #include "HlsRecorder.h" -#include "Util/File.h" +#include "FMP4/FMP4MediaSourceMuxer.h" +#include "TS/TSMediaSourceMuxer.h" using namespace std; using namespace toolkit; @@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con } return File::absolutePath(mp4FilePath, recordPath); } + case Recorder::type_hls_fmp4: { + GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); + string m3u8FilePath; + if (enableVhost) { + m3u8FilePath = tuple.shortUrl() + "/hls.fmp4.m3u8"; + } else { + m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.fmp4.m3u8"; + } + // Here we use the customized file path. + if (!customized_path.empty()) { + return File::absolutePath(m3u8FilePath, customized_path); + } + return File::absolutePath(m3u8FilePath, hlsPath); + } default: return ""; } @@ -82,6 +97,34 @@ std::shared_ptr Recorder::createRecorder(type type, const Me #endif } + case Recorder::type_hls_fmp4: { +#if defined(ENABLE_HLS_FMP4) + auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path); + GET_CONFIG(bool, enable_vhost, General::kEnableVhost); + auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option); + ret->setMediaSource(tuple); + return ret; +#else + throw std::invalid_argument("hls.fmp4相关功能未打开,请开启ENABLE_HLS_FMP4宏后编译再测试"); +#endif + } + + case Recorder::type_fmp4: { +#if defined(ENABLE_HLS_FMP4) || defined(ENABLE_MP4) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("fmp4相关功能未打开,请开启ENABLE_HLS_FMP4或ENABLE_MP4宏后编译再测试"); +#endif + } + + case Recorder::type_ts: { +#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("mpegts相关功能未打开,请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试"); +#endif + } + default: throw std::invalid_argument("未知的录制类型"); } } diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h index 7ccbc04e..42763b32 100644 --- a/src/Record/Recorder.h +++ b/src/Record/Recorder.h @@ -44,7 +44,13 @@ public: // 录制hls type_hls = 0, // 录制MP4 - type_mp4 = 1 + type_mp4 = 1, + // 录制hls.fmp4 + type_hls_fmp4 = 2, + // fmp4直播 + type_fmp4 = 3, + // ts直播 + type_ts = 4, } type; /** diff --git a/src/Rtmp/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index 51f8b46c..3f6b8bd4 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -44,7 +44,8 @@ public: return _media_src->readerCount(); } - void onAllTrackReady(){ + void addTrackCompleted() override { + RtmpMuxer::addTrackCompleted(); makeConfigPacket(); _media_src->setMetaData(getMetadata()); } diff --git a/src/Rtsp/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index c772149a..955c6341 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -44,7 +44,8 @@ public: _media_src->setTimeStamp(stamp); } - void onAllTrackReady(){ + void addTrackCompleted() override { + RtspMuxer::addTrackCompleted(); _media_src->setSdp(getSdp()); } From 3259bac046454c3303339247792c555d4fcc197e Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 2 Jul 2023 12:25:30 +0800 Subject: [PATCH 02/92] =?UTF-8?q?c=20sdk=E6=96=B0=E5=A2=9Emk=5Fframe=5Fmer?= =?UTF-8?q?ger=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/include/mk_frame.h | 49 +++++++++++++++++++++++++++++++++++++++++ api/source/mk_frame.cpp | 26 ++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/api/include/mk_frame.h b/api/include/mk_frame.h index 40ba4a4d..5df8a617 100644 --- a/api/include/mk_frame.h +++ b/api/include/mk_frame.h @@ -117,6 +117,55 @@ API_EXPORT uint64_t API_CALL mk_frame_get_pts(mk_frame frame); */ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame); +////////////////////////////////////////////////////////////////////// + +typedef struct mk_buffer_t *mk_buffer; +typedef struct mk_frame_merger_t *mk_frame_merger; + +/** + * 创建帧合并器 + * @param type 起始头类型,0: none, 1: h264_prefix/AnnexB(0x 00 00 00 01), 2: mp4_nal_size(avcC) + * @return 帧合并器 + */ +API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type); + +/** + * 销毁帧合并器 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx); + +/** + * 清空merger对象缓冲,方便复用 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx); + +/** + * 合并帧回调函数 + * @param user_data 用户数据指针 + * @param dts 解码时间戳 + * @param pts 显示时间戳 + * @param buffer 合并后数据buffer对象 + * @param have_key_frame 合并后数据中是否包含关键帧 + */ +typedef void(API_CALL *on_mk_frame_merger)(void *user_data, uint64_t dts, uint64_t pts, mk_buffer buffer, int have_key_frame); + +/** + * 输入frame到merger对象并合并 + * @param ctx 对象指针 + * @param frame 帧数据 + * @param cb 帧合并回调函数 + * @param user_data 帧合并回调函数用户数据指针 + */ +API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data); + +/** + * 强制flush merger对象缓冲,调用此api前需要确保先调用mk_frame_merger_input函数并且回调参数有效 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx); + #ifdef __cplusplus } #endif diff --git a/api/source/mk_frame.cpp b/api/source/mk_frame.cpp index 8c2e4ff6..c136d76f 100644 --- a/api/source/mk_frame.cpp +++ b/api/source/mk_frame.cpp @@ -178,3 +178,29 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) { } return ret; } + +API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type) { + return reinterpret_cast(new FrameMerger(type)); +} + +API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx) { + assert(ctx); + delete reinterpret_cast(ctx); +} + +API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(ctx)->clear(); +} + +API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(ctx)->flush(); +} + +API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data) { + assert(ctx && frame && cb); + reinterpret_cast(ctx)->inputFrame(*((Frame::Ptr *) frame), [cb, user_data](uint64_t dts, uint64_t pts, const toolkit::Buffer::Ptr &buffer, bool have_key_frame) { + cb(user_data, dts, pts, (mk_buffer)(&buffer), have_key_frame); + }); +} From f911ad7a1f79fcace31c233549002e23a1d76060 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 2 Jul 2023 12:34:44 +0800 Subject: [PATCH 03/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- README_en.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ddee13cd..b015afb8 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ ## 功能清单 ### 功能一览 -功能一览 +功能一览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -71,7 +71,7 @@ - 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) - HLS - - 支持HLS文件生成,自带HTTP文件服务器 + - 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器 - 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务 - 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4 - 支持H264/H265/AAC/G711/OPUS编码 diff --git a/README_en.md b/README_en.md index 154f51c7..d339cd87 100644 --- a/README_en.md +++ b/README_en.md @@ -45,7 +45,7 @@ ## Feature List ### Overview of Features -Overview of Features +Overview of Features - RTSP[S] - RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show @@ -70,7 +70,7 @@ - Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) - HLS - - Supports HLS file generation and comes with an HTTP file server + - Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server - Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses - Supports HLS player and can pull HLS to rtsp/rtmp/mp4 - Supports H264/H265/AAC/G711/OPUS encoding From 0b32306bf5617ffe78dac269c4aa613d14b86280 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 2 Jul 2023 12:43:28 +0800 Subject: [PATCH 04/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUTHORS | 11 ++++++++++- README.md | 9 +++++++++ README_en.md | 9 +++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 4b5e43fc..a418921d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -73,4 +73,13 @@ WuPeng [ljx0305](https://github.com/ljx0305) [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) -[PioLing](https://github.com/PioLing) \ No newline at end of file +[PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) \ No newline at end of file diff --git a/README.md b/README.md index b015afb8..b3ea16cf 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,15 @@ bash build_docker_images.sh [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) ## 使用案例 diff --git a/README_en.md b/README_en.md index d339cd87..84894a74 100644 --- a/README_en.md +++ b/README_en.md @@ -479,6 +479,15 @@ Thanks to all those who have supported this project in various ways, including b [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) ## Use Cases From 59c961a6df4c9f337fb5cc563863635bda27fc0b Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 2 Jul 2023 12:44:07 +0800 Subject: [PATCH 05/92] =?UTF-8?q?webrtc=E9=A1=B5=E9=9D=A2=E6=B5=81?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=97=B6=E9=97=B4=E6=94=B9=E4=B8=BA5?= =?UTF-8?q?=E7=A7=92=E4=B8=80=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- www/webrtc/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 0ff074f4..c39d6c04 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -316,7 +316,7 @@ } setInterval(() => { get_media_list(); - }, 1000); + }, 5000); From 6b9889a883a91d7ba906f44f3c5288311dc883a4 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 2 Jul 2023 12:45:07 +0800 Subject: [PATCH 06/92] =?UTF-8?q?WebApi=E8=B0=83=E8=AF=95=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=81=A2=E5=A4=8D=E4=B8=BADebug=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 2fe75f73..6742a957 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -247,7 +247,7 @@ static inline void addHttpListener(){ size = body->remainSize(); } - LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__); + LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__); log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n"; log << "# header:\r\n"; From 7c6b612a4d0a0b966c4bdb7f4475b2d037408f53 Mon Sep 17 00:00:00 2001 From: xiangshengjye <46069012+xiangshengjye@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:11:21 +0800 Subject: [PATCH 07/92] =?UTF-8?q?c=20sdk=E6=96=B0=E5=A2=9Empeg-ps/ts?= =?UTF-8?q?=E6=89=93=E5=8C=85=E6=8E=A5=E5=8F=A3=20(#2620)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/include/mk_frame.h | 53 +++++++++++++++++++++++++++++++++ api/source/mk_frame.cpp | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/api/include/mk_frame.h b/api/include/mk_frame.h index 5df8a617..62498096 100644 --- a/api/include/mk_frame.h +++ b/api/include/mk_frame.h @@ -166,6 +166,59 @@ API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame fra */ API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx); +////////////////////////////////////////////////////////////////////// + +typedef struct mk_mpeg_muxer_t *mk_mpeg_muxer; + +/** + * mpeg-ps/ts 打包器输出回调函数 + * @param user_data 设置回调时的用户数据指针 + * @param muxer 对象 + * @param frame 帧数据 + * @param size 帧数据长度 + * @param timestamp 时间戳 + * @param key_pos 是否关键帧 + */ +typedef void(API_CALL *on_mk_mpeg_muxer_frame)(void *user_data, mk_mpeg_muxer muxer, const char *frame, size_t size, uint64_t timestamp, int key_pos); + +/** + * mpeg-ps/ts 打包器 + * @param cb 打包回调函数 + * @param user_data 回调用户数据指针 + * @param is_ps 是否是ps + * @return 打包器对象 + */ +API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps); + +/** + * 删除mpeg-ps/ts 打包器 + * @param ctx 打包器 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx); + +/** + * 添加音视频track + * @param ctx mk_mpeg_muxer对象 + * @param track mk_track对象,音视频轨道 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track); + +/** + * 初始化track完毕后调用此函数, + * 在单track(只有音频或视频)时,因为ZLMediaKit不知道后续是否还要添加track,所以会多等待3秒钟 + * 如果产生的流是单Track类型,请调用此函数以便加快流生成速度,当然不调用该函数,影响也不大(会多等待3秒) + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx); + +/** + * 输入frame对象 + * @param ctx mk_mpeg_muxer对象 + * @param frame 帧对象 + * @return 1代表成功,0失败 + */ +API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame); + #ifdef __cplusplus } #endif diff --git a/api/source/mk_frame.cpp b/api/source/mk_frame.cpp index c136d76f..7a7cdb85 100644 --- a/api/source/mk_frame.cpp +++ b/api/source/mk_frame.cpp @@ -9,10 +9,12 @@ */ #include "mk_frame.h" +#include "mk_track.h" #include "Extension/Frame.h" #include "Extension/H264.h" #include "Extension/H265.h" #include "Extension/AAC.h" +#include "Record/MPEG.h" using namespace mediakit; @@ -204,3 +206,66 @@ API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame fra cb(user_data, dts, pts, (mk_buffer)(&buffer), have_key_frame); }); } + +////////////////////////////////////////////////////////////////////// + +class MpegMuxerForC : public MpegMuxer { +public: + using onMuxer = std::function; + MpegMuxerForC(bool is_ps) : MpegMuxer(is_ps) { + _cb = nullptr; + } + ~MpegMuxerForC() { MpegMuxer::flush(); }; + + void setOnMuxer(onMuxer cb) { + _cb = std::move(cb); + } + +private: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (_cb) { + if (!buffer) { + _cb(nullptr, 0, timestamp, key_pos); + } else { + _cb(buffer->data(), buffer->size(), timestamp, key_pos); + } + } + } + +private: + onMuxer _cb; +}; + +API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps){ + assert(cb); + auto ret = new MpegMuxerForC(is_ps); + std::shared_ptr ptr(user_data, [](void *) {}); + ret->setOnMuxer([cb, ptr, ret](const char *frame, size_t size, uint64_t timestamp, int key_pos) { + cb(ptr.get(), reinterpret_cast(ret), frame, size, timestamp, key_pos); + }); + return reinterpret_cast(ret); +} + +API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx){ + assert(ctx); + auto ptr = reinterpret_cast(ctx); + delete ptr; +} + +API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track) { + assert(ctx && track); + auto ptr = reinterpret_cast(ctx); + ptr->addTrack(*((Track::Ptr *) track)); +} + +API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx) { + assert(ctx); + auto ptr = reinterpret_cast(ctx); + ptr->addTrackCompleted(); +} + +API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame){ + assert(ctx && frame); + auto ptr = reinterpret_cast(ctx); + return ptr->inputFrame(*((Frame::Ptr *) frame)); +} \ No newline at end of file From 788915c7f7ca7198c5498880491aae41a1881c6e Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Thu, 6 Jul 2023 15:47:30 +0800 Subject: [PATCH 08/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dmp4=E8=A7=A3=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=E5=99=A8=E8=BE=93=E5=87=BAsps=20pps=E5=B8=A7=E6=97=B6?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E8=B5=B7=E5=A7=8B=E5=A4=B4=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98=20(#2627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mp4解复用器在解析mp4文件时,当获取到mp4头中sps/pps等配置帧信息时,创建相应的Frame未设置prefix size;导致后续输入H264Track分帧逻辑异常。 --- src/Record/MP4Demuxer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Record/MP4Demuxer.cpp b/src/Record/MP4Demuxer.cpp index a1bbd34c..a3f29958 100644 --- a/src/Record/MP4Demuxer.cpp +++ b/src/Record/MP4Demuxer.cpp @@ -102,7 +102,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_avc_to_nalu(&avc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *)config, size, 0, 4)); + video->inputFrame(std::make_shared((char *)config, size, 0, 0,4)); } } } @@ -117,7 +117,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *) config, size, 0, 4)); + video->inputFrame(std::make_shared((char *) config, size, 0, 0,4)); } } } From c86e6ba3f1093a4bfae8f92360ff5c8169a8edfc Mon Sep 17 00:00:00 2001 From: a-ucontrol <55526028+a-ucontrol@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:07:49 +0300 Subject: [PATCH 09/92] Fix build issue with uclibc (#2625 #2631) --- src/Common/Parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 3e793039..6fa5aa2d 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -53,7 +53,7 @@ void Parser::parse(const char *buf, size_t size) { if (ptr == buf) { auto blank = strchr(ptr, ' '); CHECK(blank > ptr && blank < next_line); - _method = std::string(ptr, blank); + _method = std::string(ptr, blank - ptr); auto next_blank = strchr(blank + 1, ' '); CHECK(next_blank && next_blank < next_line); _url.assign(blank + 1, next_blank); @@ -67,7 +67,7 @@ void Parser::parse(const char *buf, size_t size) { } else { auto pos = strchr(ptr, ':'); CHECK(pos > ptr && pos < next_line); - std::string key { ptr, pos }; + std::string key { ptr, static_cast(pos - ptr) }; std::string value; if (pos[1] == ' ') { value.assign(pos + 2, next_line); From 3e9cc64894c44ca4ee907ffbfbe2975c31791066 Mon Sep 17 00:00:00 2001 From: tjpgt <602950305@qq.com> Date: Fri, 7 Jul 2023 14:57:23 +0800 Subject: [PATCH 10/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwebrtc=20demo=E7=BD=91?= =?UTF-8?q?=E9=A1=B5=E4=B8=ADstream=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF=20?= =?UTF-8?q?(#2636)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- www/webrtc/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index c39d6c04..78fb905b 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -284,7 +284,7 @@ ss[o.app].add(o.stream); } else { let set = new Set(); - set.add(o.steram); + set.add(o.stream); ss[o.app] = set; } } From 07a1a929ad4a41016c1c666987243156cc6ded73 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 8 Jul 2023 10:18:09 +0800 Subject: [PATCH 11/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8DHLS=E7=82=B9=E6=92=AD?= =?UTF-8?q?=E6=97=B6=E4=B8=8B=E8=BD=BD=E5=88=87=E7=89=87=E8=BF=87=E5=BF=AB?= =?UTF-8?q?=E7=9A=84bug=20(#2635=20#2628)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: xia-chu <771730766@qq.com> --- src/Http/HlsPlayer.cpp | 26 ++-- tests/test_bench_forward.cpp | 267 +++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 tests/test_bench_forward.cpp diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp index 1de45cf8..c6509c0c 100644 --- a/src/Http/HlsPlayer.cpp +++ b/src/Http/HlsPlayer.cpp @@ -71,6 +71,11 @@ void HlsPlayer::teardown() { void HlsPlayer::fetchSegment() { if (_ts_list.empty()) { + // 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628 + if(!HlsParser::isLive()){ + teardown(); + return; + } //播放列表为空,那么立即重新下载m3u8文件 _timer.reset(); fetchIndexFile(); @@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() { WarnL << "Download ts segment " << url << " failed:" << err; if (err.getErrCode() == Err_timeout) { strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE); - }else{ - strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE); + } else { + strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE); } } - //提前半秒下载好 - auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5; - if (delay <= 0) { - //延时最小10ms - delay = 10; + // 提前0.5秒下载好,支持点播文件控制下载速度: #2628 + auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f; + if (delay > 2.0) { + // 提前1秒下载 + delay -= 1.0; + } else if (delay <= 0) { + // 延时最小10ms + delay = 0.01; } - //延时下载下一个切片 - strong_self->_timer_ts.reset(new Timer(delay / 1000.0f, [weak_self]() { + // 延时下载下一个切片 + strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() { auto strong_self = weak_self.lock(); if (strong_self) { strong_self->fetchSegment(); diff --git a/tests/test_bench_forward.cpp b/tests/test_bench_forward.cpp new file mode 100644 index 00000000..634d7b8e --- /dev/null +++ b/tests/test_bench_forward.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include "Util/logger.h" +#include "Util/onceToken.h" +#include "Util/CMD.h" +#include "Util/File.h" +#include "Common/config.h" +#include "Common/Parser.h" +#include "Rtsp/Rtsp.h" +#include "Thread/WorkThreadPool.h" +#include "Pusher/MediaPusher.h" +#include "Player/PlayerProxy.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class CMD_main : public CMD { +public: + CMD_main() { + _parser.reset(new OptionParser(nullptr)); + + (*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/ + "level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(LTrace).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/ + nullptr); + + + (*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/ + "threads",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(thread::hardware_concurrency()).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动事件触发线程数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('i',/*该选项简称,如果是\x00则说明无简称*/ + "inputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/inputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "拉流地址配置文件,支持rtmp、rtsp, hls,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('o',/*该选项简称,如果是\x00则说明无简称*/ + "outputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/outputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "推流地址配置文件,支持rtmp、rtsp,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('d',/*该选项简称,如果是\x00则说明无简称*/ + "delay",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "50",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动拉流代理间隔,单位毫秒",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('m',/*该选项简称,如果是\x00则说明无简称*/ + "merge",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "300",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "合并写毫秒,合并写能提高性能",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('T',/*该选项简称,如果是\x00则说明无简称*/ + "rtp",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string((int) (Rtsp::RTP_TCP)).data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "rtsp拉流方式,支持tcp/udp/multicast:0/1/2",/*该选项说明文字*/ + nullptr); + + } + + ~CMD_main() override {} + + const char *description() const override { + return "主程序命令参数"; + } +}; + + +//此程序为zlm的转推性能测试工具,用于测试拉流代理转推性能 +int main(int argc, char *argv[]) { + CMD_main cmd_main; + try { + cmd_main.operator()(argc, argv); + } catch (ExitException &) { + return 0; + } catch (std::exception &ex) { + cout << ex.what() << endl; + return -1; + } + + int threads = cmd_main["threads"]; + LogLevel logLevel = (LogLevel) cmd_main["level"].as(); + logLevel = MIN(MAX(logLevel, LTrace), LError); + auto in_urls = cmd_main["inputs"]; + auto out_urls = cmd_main["outputs"]; + auto rtp_type = cmd_main["rtp"].as(); + auto delay_ms = cmd_main["delay"].as(); + auto merge_ms = cmd_main["merge"].as(); + + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + + //设置线程数 + EventPollerPool::setPoolSize(threads); + WorkThreadPool::setPoolSize(threads); + + //设置合并写 + mINI::Instance()[General::kMergeWriteMS] = merge_ms; + + + std::vector input_urls; + std::vector output_urls; + + auto parse_urls = [&]() { + // 获取输入源列表 + auto inputs = ::split(toolkit::File::loadFile(in_urls.c_str()), "\n"); + for(auto &url : inputs){ + if(url.empty() || url.find("://") == std::string::npos) { + continue; + } + auto input_url = ::trim(url); + input_urls.emplace_back(input_url); + } + // 获取输出源列表 + auto outputs = ::split(toolkit::File::loadFile(out_urls.c_str()), "\n"); + for(auto &url : outputs){ + if(url.empty() || url.find("://") == std::string::npos){ + continue; + } + auto output_url = ::trim(url); + output_urls.emplace_back(output_url); + } + + if(input_urls.empty() || input_urls.size() != output_urls.size()){ + return -1; + } + + for(size_t i = 0; i < input_urls.size(); i++){ + InfoL << "拉流地址: " << input_urls[i] << ",推流地址:" << output_urls[i]; + } + return 0; + }; + + if (0 != parse_urls()){ + cout << "请检查inputs和outputs文件是否正确!" << endl; + return -1; + } + + //推流器map + recursive_mutex mtx; + unordered_map proxy_map; + unordered_map pusher_map; + + auto add_pusher = [&](const MediaSource::Ptr &src, const string &url, int index) { + auto pusher = std::make_shared(src); + pusher->setOnCreateSocket([](const EventPoller::Ptr &poller) { + //socket关闭互斥锁,提高性能 + return std::make_shared(poller, false); + }); + //设置推流失败监听 + pusher->setOnPublished([&mtx, &pusher_map, index](const SockException &ex) { + if (ex) { + //推流失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + } + }); + //设置推流中途断开监听 + pusher->setOnShutdown([&mtx, &pusher_map, index](const SockException &ex) { + //推流中途失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + }); + //设置rtsp推流方式(在rtsp推流时有效) + (*pusher)[Client::kRtpType] = rtp_type; + pusher->publish(url); + //保持对象不销毁 + lock_guard lck(mtx); + pusher_map.emplace(index, std::move(pusher)); + //休眠后再启动下一个推流,防止短时间海量链接 + if (delay_ms > 0) { + usleep(1000 * delay_ms); + } + }; + + // 添加转推任务 + for(size_t i = 0; i < input_urls.size(); i++) { + //休眠一秒打印 + sleep(1); + auto schema = findSubString(output_urls[i].data(), nullptr, "://"); + if (schema != RTSP_SCHEMA && schema != RTMP_SCHEMA) { + cout << "推流协议只支持rtsp或rtmp!" << endl; + return -1; + } + ProtocolOption option; + option.enable_ts = false; + option.enable_fmp4 = false; + option.enable_hls = false; + option.enable_mp4 = false; + option.modify_stamp = (int)ProtocolOption::kModifyStampRelative; + //添加拉流代理 + auto proxy = std::make_shared(DEFAULT_VHOST, "app", std::to_string(i), option, -1, nullptr, 1); + //开始拉流代理 + proxy->play(input_urls[i]); + proxy_map.emplace(i, std::move(proxy)); + } + + // 设置退出信号 + static bool exit_flag = false; + signal(SIGINT, [](int) { exit_flag = true; }); + while (!exit_flag) { + //休眠一秒打印 + sleep(1); + + size_t alive_pusher = 0; + { + lock_guard lck(mtx); + alive_pusher = pusher_map.size(); + } + InfoL << "在线转推器个数:" << alive_pusher; + + auto find_pusher = [&](int index){ + lock_guard lck(mtx); + auto it = pusher_map.find(index); + if (it == pusher_map.end()){ + return false; + } + return true; + }; + for(size_t i = 0; i < input_urls.size(); i++) { + if (!find_pusher(i)){ + auto input_url = input_urls[i]; + auto src = MediaSource::find(RTMP_SCHEMA, DEFAULT_VHOST, "app", std::to_string(i), false); + if (src != nullptr){ + add_pusher(src,output_urls[i],i); + } + } + } + } + + return 0; +} \ No newline at end of file From 575a4263c0c73fd9803f036be935a22dc95b0737 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 8 Jul 2023 10:19:43 +0800 Subject: [PATCH 12/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0ZLToolKit,=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dhttp/hls=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=97=A0=E6=95=88socket=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index e4744a0a..79db405b 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit e4744a0a523817356f2ec995ee5a732264c31629 +Subproject commit 79db405ba43c29453c60c3e054d863ce6bd1ef29 From 077566d1e16653f6338e0c301c3344c14a158c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 8 Jul 2023 21:32:04 +0800 Subject: [PATCH 13/92] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81on=5F?= =?UTF-8?q?publish=20hook=E8=BF=94=E5=9B=9Estream=5Freplace=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E6=B5=81id=20(#2641)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/MediaSource.h | 4 ++++ src/Common/MultiMediaSourceMuxer.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 3438e350..0f58456d 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -187,6 +187,9 @@ public: //hls录制保存路径 std::string hls_save_path; + // 支持通过on_publish返回值替换stream_id + std::string stream_replace; + template ProtocolOption(const MAP &allArgs) : ProtocolOption() { #define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) @@ -214,6 +217,7 @@ public: GET_OPT_VALUE(mp4_save_path); GET_OPT_VALUE(hls_save_path); + GET_OPT_VALUE(stream_replace); } private: diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index f70bc2a1..40fb1eb6 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -80,6 +80,10 @@ std::string MultiMediaSourceMuxer::shortUrl() const { } MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) { + if (!option.stream_replace.empty()) { + // 支持在on_publish hook中替换stream_id + _tuple.stream = option.stream_replace; + } _poller = EventPollerPool::Instance().getPoller(); _create_in_poller = _poller->isCurrentThread(); _option = option; From fad8dd74e77d2848a7c8cc738a5a07262cc72e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 8 Jul 2023 21:33:07 +0800 Subject: [PATCH 14/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwebrtc=E5=BC=80?= =?UTF-8?q?=E5=90=AFsimulcast=E6=8E=A8=E6=B5=81=E6=97=B6=EF=BC=8C=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E8=A7=82=E7=9C=8B=E4=BA=BA=E6=95=B0=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E7=9B=B8=E5=85=B3bug=20(#2640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/WebRtcPusher.cpp | 12 ++++++++---- webrtc/WebRtcPusher.h | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index e8e46a40..5d6a68d2 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -59,11 +59,14 @@ bool WebRtcPusher::close(MediaSource &sender) { } int WebRtcPusher::totalReaderCount(MediaSource &sender) { - auto total_count = 0; - for (auto &src : _push_src_sim) { - total_count += src.second->totalReaderCount(); + auto total_count = _push_src ? _push_src->totalReaderCount() : 0; + if (_simulcast) { + std::lock_guard lock(_mtx); + for (auto &src : _push_src_sim) { + total_count += src.second->totalReaderCount(); + } } - return total_count + _push_src->totalReaderCount(); + return total_count; } MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { @@ -96,6 +99,7 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt } } else { //视频 + std::lock_guard lock(_mtx); auto &src = _push_src_sim[rid]; if (!src) { const auto& stream = _push_src->getMediaTuple().stream; diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index 32c47055..b35f9e98 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -65,6 +65,7 @@ private: //推流所有权 std::shared_ptr _push_src_ownership; //推流的rtsp源,支持simulcast + std::mutex _mtx; std::unordered_map _push_src_sim; std::unordered_map > _push_src_sim_ownership; }; From e52c1cc510eab18d307cc362c8820547bb2781bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 8 Jul 2023 21:35:09 +0800 Subject: [PATCH 15/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8DaddFFmpegSource?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9A=84=E6=B5=81=E4=BA=8B=E4=BB=B6=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E5=8F=AF=E8=83=BD=E5=A4=B1=E6=95=88=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#2642=20#2629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此pr主要为了修复 #2629,通过新增getMuxer接口, 可以直接获取到所有协议共享的MultiMediaSourceMuxer对象, 在此对象完成事件拦截,防止某种协议事件丢失。 同时调整了下FFmpegSource.cpp代码格式。 --- server/FFmpegSource.cpp | 163 +++++++++++++-------------- server/FFmpegSource.h | 2 - src/Common/MediaSource.cpp | 26 ++--- src/Common/MediaSource.h | 8 +- src/Common/MultiMediaSourceMuxer.cpp | 12 ++ src/Common/MultiMediaSourceMuxer.h | 10 +- 6 files changed, 115 insertions(+), 106 deletions(-) diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index fa9044ed..8363950c 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -11,6 +11,7 @@ #include "FFmpegSource.h" #include "Common/config.h" #include "Common/MediaSource.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Util/File.h" #include "System.h" #include "Thread/WorkThreadPool.h" @@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){ _enable_mp4 = enable_mp4; } -void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); +void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) { + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); _src_url = src_url; _dst_url = dst_url; @@ -91,120 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key); if (cmd_it != mINI::Instance().end()) { ffmpeg_cmd = cmd_it->second; - } else{ + } else { WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")"; } } - char cmd[2048] = {0}; + char cmd[2048] = { 0 }; snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data()); auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log); _process.run(cmd, log_file); InfoL << cmd; if (is_local_ip(_media_info.host)) { - //推流给自己的,通过判断流是否注册上来判断是否正常 + // 推流给自己的,通过判断流是否注册上来判断是否正常 if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) { - cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流")); + cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流")); return; } weak_ptr weakSelf = shared_from_this(); - findAsync(timeout_ms,[cb,weakSelf,timeout_ms](const MediaSource::Ptr &src){ + findAsync(timeout_ms, [cb, weakSelf, timeout_ms](const MediaSource::Ptr &src) { auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - //自己已经销毁 + if (!strongSelf) { + // 自己已经销毁 return; } - if(src){ - //推流给自己成功 + if (src) { + // 推流给自己成功 cb(SockException()); strongSelf->onGetMediaSource(src); strongSelf->startTimer(timeout_ms); return; } //推流失败 - if(!strongSelf->_process.wait(false)){ - //ffmpeg进程已经退出 - cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); + if (!strongSelf->_process.wait(false)) { + // ffmpeg进程已经退出 + cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); return; } - //ffmpeg进程还在线,但是等待推流超时 - cb(SockException(Err_other,"等待超时")); + // ffmpeg进程还在线,但是等待推流超时 + cb(SockException(Err_other, "等待超时")); }); } else{ //推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功 weak_ptr weakSelf = shared_from_this(); - _timer = std::make_shared(timeout_ms / 1000.0f,[weakSelf,cb,timeout_ms](){ + _timer = std::make_shared(timeout_ms / 1000.0f, [weakSelf, cb, timeout_ms]() { auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - //自身已经销毁 + if (!strongSelf) { + // 自身已经销毁 return false; } - //FFmpeg还在线,那么我们认为推流成功 - if(strongSelf->_process.wait(false)){ + // FFmpeg还在线,那么我们认为推流成功 + if (strongSelf->_process.wait(false)) { cb(SockException()); strongSelf->startTimer(timeout_ms); return false; } - //ffmpeg进程已经退出 - cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); + // ffmpeg进程已经退出 + cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); return false; - },_poller); + }, _poller); } } void FFmpegSource::findAsync(int maxWaitMS, const function &cb) { - auto src = MediaSource::find(_media_info.schema, - _media_info.vhost, - _media_info.app, - _media_info.stream); - if(src || !maxWaitMS){ + auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream); + if (src || !maxWaitMS) { cb(src); return; } void *listener_tag = this; - //若干秒后执行等待媒体注册超时回调 - auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){ - //取消监听该事件 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 若干秒后执行等待媒体注册超时回调 + auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() { + // 取消监听该事件 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); cb(nullptr); return 0; }); weak_ptr weakSelf = shared_from_this(); - auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) { + auto onRegist = [listener_tag, weakSelf, cb, onRegistTimeout](BroadcastMediaChangedArgs) { auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - //本身已经销毁,取消延时任务 + if (!strongSelf) { + // 本身已经销毁,取消延时任务 onRegistTimeout->cancel(); - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); return; } - if (!bRegist || - sender.getSchema() != strongSelf->_media_info.schema || + if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema || !equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) { - //不是自己感兴趣的事件,忽略之 + // 不是自己感兴趣的事件,忽略之 return; } - //查找的流终于注册上了;取消延时任务,防止多次回调 + // 查找的流终于注册上了;取消延时任务,防止多次回调 onRegistTimeout->cancel(); - //取消事件监听 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 取消事件监听 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); - //切换到自己的线程再回复 - strongSelf->_poller->async([weakSelf,cb](){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - return; + // 切换到自己的线程再回复 + strongSelf->_poller->async([weakSelf, cb]() { + if (auto strongSelf = weakSelf.lock()) { + // 再找一遍媒体源,一般能找到 + strongSelf->findAsync(0, cb); } - //再找一遍媒体源,一般能找到 - strongSelf->findAsync(0,cb); }, false); }; - //监听媒体注册事件 + // 监听媒体注册事件 NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist); } @@ -222,49 +217,49 @@ void FFmpegSource::startTimer(int timeout_ms) { } bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000; if (is_local_ip(strongSelf->_media_info.host)) { - //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 + // 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) { - //同步查找流 + // 同步查找流 if (!src || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 - if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){ - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 + if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) { + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->_replay_ticker.resetTime(); strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); } } }); } else { - //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 + // 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 if (!strongSelf->_process.wait(false) || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //ffmpeg不在线,重新拉流 + // ffmpeg不在线,重新拉流 strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) { - if(!ex){ - //没有错误 + if (!ex) { + // 没有错误 return; } auto strongSelf = weakSelf.lock(); if (!strongSelf) { - //自身已经销毁 + // 自身已经销毁 return; } - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->startTimer(10 * 1000); }); } @@ -294,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{ return MediaOriginType::ffmpeg_pull; } -string FFmpegSource::getOriginUrl(MediaSource &sender) const{ +string FFmpegSource::getOriginUrl(MediaSource &sender) const { return _src_url; } -std::shared_ptr FFmpegSource::getOriginSock(MediaSource &sender) const { - return nullptr; -} - void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { - auto listener = src->getListener(true); - if (listener.lock().get() != this) { + auto muxer = src->getMuxer(); + auto listener = muxer ? muxer->getDelegate() : nullptr; + if (listener && listener.get() != this) { //防止多次进入onGetMediaSource函数导致无限递归调用的bug setDelegate(listener); - src->setListener(shared_from_this()); + muxer->setDelegate(shared_from_this()); if (_enable_hls) { src->setupRecord(Recorder::type_hls, true, "", 0); } @@ -318,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { } void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); Ticker ticker; - WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){ + WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() { auto elapsed_ms = ticker.elapsedTime(); if (elapsed_ms > timeout_sec * 1000) { - //超时,后台线程负载太高,当代太久才启动该任务 + // 超时,后台线程负载太高,当代太久才启动该任务 cb(false, "wait work poller schedule snap task timeout"); return; } @@ -346,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float return 0; }); - //等待FFmpeg进程退出 + // 等待FFmpeg进程退出 process->wait(true); // FFmpeg进程退出了可以取消定时器了 delayTask->cancel(); - //执行回调函数 + // 执行回调函数 bool success = process->exit_code() == 0 && File::fileSize(save_path.data()); cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : ""); }); } - diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index e85df3ec..68e3c440 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -79,8 +79,6 @@ private: mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; //获取媒体源url或者文件路径 std::string getOriginUrl(mediakit::MediaSource &sender) const override; - // 获取媒体源客户端相关信息 - std::shared_ptr getOriginSock(mediakit::MediaSource &sender) const override; private: bool _enable_hls = false; diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 16adfd8d..9b494f16 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -172,20 +172,8 @@ void MediaSource::setListener(const std::weak_ptr &listener){ _listener = listener; } -std::weak_ptr MediaSource::getListener(bool next) const{ - if (!next) { - return _listener; - } - - auto listener = dynamic_pointer_cast(_listener.lock()); - if (!listener) { - //不是MediaSourceEventInterceptor对象或者对象已经销毁 - return _listener; - } - //获取被拦截的对象 - auto next_obj = listener->getDelegate(); - //有则返回之 - return next_obj ? next_obj : _listener; +std::weak_ptr MediaSource::getListener() const { + return _listener; } int MediaSource::totalReaderCount(){ @@ -277,6 +265,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() { throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl()); } +std::shared_ptr MediaSource::getMuxer() { + auto listener = _listener.lock(); + return listener ? listener->getMuxer(*this) : nullptr; +} + void MediaSource::onReaderChanged(int size) { try { weak_ptr weak_self = shared_from_this(); @@ -780,6 +773,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed"); } +std::shared_ptr MediaSourceEventInterceptor::getMuxer(MediaSource &sender) { + auto listener = _listener.lock(); + return listener ? listener->getMuxer(sender) : nullptr; +} + bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { auto listener = _listener.lock(); if (!listener) { diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 0f58456d..8d2c7658 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t { std::string getOriginTypeString(MediaOriginType type); class MediaSource; +class MultiMediaSourceMuxer; class MediaSourceEvent { public: friend class MediaSource; @@ -88,6 +89,8 @@ public: virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; } // 获取所有track相关信息 virtual std::vector getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector(); }; + // 获取MultiMediaSourceMuxer对象 + virtual std::shared_ptr getMuxer(MediaSource &sender) { return nullptr; } class SendRtpArgs { public: @@ -257,6 +260,7 @@ public: bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override; float getLossRate(MediaSource &sender, TrackType type) override; toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + std::shared_ptr getMuxer(MediaSource &sender) override; private: std::weak_ptr _listener; @@ -330,7 +334,7 @@ public: // 设置监听者 virtual void setListener(const std::weak_ptr &listener); // 获取监听者 - std::weak_ptr getListener(bool next = false) const; + std::weak_ptr getListener() const; // 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数 virtual int readerCount() = 0; @@ -372,6 +376,8 @@ public: float getLossRate(mediakit::TrackType type); // 获取所在线程 toolkit::EventPoller::Ptr getOwnerPoller(); + // 获取MultiMediaSourceMuxer对象 + std::shared_ptr getMuxer(); ////////////////static方法,查找或生成MediaSource//////////////// diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 40fb1eb6..0446abf6 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -71,6 +71,14 @@ static string getTrackInfoStr(const TrackSource *track_src){ return std::move(codec_info); } +const ProtocolOption &MultiMediaSourceMuxer::getOption() const { + return _option; +} + +const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const { + return _tuple; +} + std::string MultiMediaSourceMuxer::shortUrl() const { auto ret = getOriginUrl(MediaSource::NullMediaSource()); if (!ret.empty()) { @@ -361,6 +369,10 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { } } +std::shared_ptr MultiMediaSourceMuxer::getMuxer(MediaSource &sender) { + return shared_from_this(); +} + bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { bool ret = false; if (_rtmp) { diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index 22b53c75..4db34d42 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -126,9 +126,13 @@ public: */ toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - const MediaTuple& getMediaTuple() const { - return _tuple; - } + /** + * 获取本对象 + */ + std::shared_ptr getMuxer(MediaSource &sender) override; + + const ProtocolOption &getOption() const; + const MediaTuple &getMediaTuple() const; std::string shortUrl() const; protected: From beae515bb2bd1f68522bea3ef957c0a696c6162f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 8 Jul 2023 21:36:34 +0800 Subject: [PATCH 16/92] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A0=E4=BA=BA?= =?UTF-8?q?=E8=A7=82=E7=9C=8B=E8=87=AA=E5=8A=A8=E5=85=B3=E9=97=AD=E6=B5=81?= =?UTF-8?q?(=E6=97=A0=E9=A1=BBhook=E8=BF=94=E5=9B=9E)=E5=8A=9F=E8=83=BD=20?= =?UTF-8?q?(#2643)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auto_close参数适用于配置文件、addStreamProxy接口、on_publish hook --- conf/config.ini | 5 +++++ src/Common/MediaSource.cpp | 13 +++++++++++-- src/Common/MediaSource.h | 6 ++++++ src/Common/config.cpp | 2 ++ src/Common/config.h | 4 ++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index 105a76ad..c6a7e545 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -40,6 +40,11 @@ modify_stamp=2 enable_audio=1 #添加acc静音音频,在关闭音频时,此开关无效 add_mute_audio=1 +#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +#而是将直接关闭流 +auto_close=0 + #推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 #置0关闭此特性(推流断开会导致立即断开播放器) #此参数不应大于播放器超时时间;单位毫秒 diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 9b494f16..fc1d64f4 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -56,6 +56,7 @@ ProtocolOption::ProtocolOption() { GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp); GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio); GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio); + GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose); GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); @@ -81,6 +82,7 @@ ProtocolOption::ProtocolOption() { modify_stamp = s_modify_stamp; enable_audio = s_enabel_audio; add_mute_audio = s_add_mute_audio; + auto_close = s_auto_close; continue_push_ms = s_continue_push_ms; enable_hls = s_enable_hls; @@ -658,8 +660,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ } if (!is_mp4_vod) { - //直播时触发无人观看事件,让开发者自行选择是否关闭 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + auto muxer = strong_sender->getMuxer(); + if (muxer && muxer->getOption().auto_close) { + // 此流被标记为无人观看自动关闭流 + WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl(); + strong_sender->close(false); + } else { + // 直播时触发无人观看事件,让开发者自行选择是否关闭 + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + } } else { //这个是mp4点播,我们自动关闭 WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 8d2c7658..1329b196 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -151,6 +151,11 @@ public: bool enable_audio; //添加静音音频,在关闭音频时,此开关无效 bool add_mute_audio; + // 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) + // 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, + // 而是将直接关闭流 + bool auto_close; + //断连续推延时,单位毫秒,默认采用配置文件 uint32_t continue_push_ms; @@ -199,6 +204,7 @@ public: GET_OPT_VALUE(modify_stamp); GET_OPT_VALUE(enable_audio); GET_OPT_VALUE(add_mute_audio); + GET_OPT_VALUE(auto_close); GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(enable_hls); diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 2e6b9421..52c79091 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -99,6 +99,7 @@ namespace Protocol { const string kModifyStamp = PROTOCOL_FIELD "modify_stamp"; const string kEnableAudio = PROTOCOL_FIELD "enable_audio"; const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; +const string kAutoClose = PROTOCOL_FIELD "auto_close"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kEnableHls = PROTOCOL_FIELD "enable_hls"; @@ -126,6 +127,7 @@ static onceToken token([]() { mINI::Instance()[kEnableAudio] = 1; mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kContinuePushMS] = 15000; + mINI::Instance()[kAutoClose] = 0; mINI::Instance()[kEnableHls] = 1; mINI::Instance()[kEnableHlsFmp4] = 0; diff --git a/src/Common/config.h b/src/Common/config.h index 9f043ecb..baafac2d 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -190,6 +190,10 @@ extern const std::string kModifyStamp; extern const std::string kEnableAudio; //添加静音音频,在关闭音频时,此开关无效 extern const std::string kAddMuteAudio; +// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +// 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +// 而是将直接关闭流 +extern const std::string kAutoClose; //断连续推延时,单位毫秒,默认采用配置文件 extern const std::string kContinuePushMS; From 97859a995b5e22580d7097dd904e81a9dbc9cf08 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 8 Jul 2023 22:07:32 +0800 Subject: [PATCH 17/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0addStreamProxy=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- postman/ZLMediaKit.postman_collection.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index ad1929bd..de591c15 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -516,7 +516,13 @@ { "key": "enable_hls", "value": null, - "description": "是否转hls", + "description": "是否转hls-ts", + "disabled": true + }, + { + "key": "enable_hls_fmp4", + "value": null, + "description": "是否转hls-fmp4", "disabled": true }, { @@ -584,6 +590,12 @@ "value": null, "description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正", "disabled": true + }, + { + "key": "auto_close", + "value": null, + "description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)", + "disabled": true } ] } From e6a19c4eccfeac1f3f06433469379f0e75cbf017 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 8 Jul 2023 22:12:56 +0800 Subject: [PATCH 18/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dc=20api=E5=BD=95?= =?UTF-8?q?=E5=88=B6=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/include/mk_recorder.h | 4 ++-- api/source/mk_recorder.cpp | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/api/include/mk_recorder.h b/api/include/mk_recorder.h index c9053db5..a23e50f4 100644 --- a/api/include/mk_recorder.h +++ b/api/include/mk_recorder.h @@ -58,7 +58,7 @@ API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, co /** * 开始录制 - * @param type 0:hls,1:MP4 + * @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts * @param vhost 虚拟主机 * @param app 应用名 * @param stream 流id @@ -70,7 +70,7 @@ API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const cha /** * 停止录制 - * @param type 0:hls,1:MP4 + * @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts * @param vhost 虚拟主机 * @param app 应用名 * @param stream 流id diff --git a/api/source/mk_recorder.cpp b/api/source/mk_recorder.cpp index e16a64c4..f99d7130 100644 --- a/api/source/mk_recorder.cpp +++ b/api/source/mk_recorder.cpp @@ -47,21 +47,25 @@ static inline bool isRecording(Recorder::type type, const string &vhost, const s return src->isRecording(type); } -static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path, size_t max_second){ +static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, size_t max_second) { auto src = MediaSource::find(vhost, app, stream_id); if (!src) { WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id; return false; } - return src->setupRecord(type, true, customized_path, max_second); + bool ret; + src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, true, customized_path, max_second); }); + return ret; } -static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id){ +static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) { auto src = MediaSource::find(vhost, app, stream_id); - if(!src){ + if (!src) { return false; } - return src->setupRecord(type, false, "", 0); + bool ret; + src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, false, "", 0); }); + return ret; } API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, const char *app, const char *stream){ From f55e13f9dac22a6cc6241836b382299a64da8344 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 8 Jul 2023 22:34:09 +0800 Subject: [PATCH 19/92] =?UTF-8?q?c=20sdk=E6=96=B0=E5=A2=9Eon=5Fmk=5Fmedia?= =?UTF-8?q?=5Fsend=5Frtp=5Fstop=E4=BA=8B=E4=BB=B6=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/include/mk_events.h | 11 +++++++++++ api/source/mk_events.cpp | 7 +++++++ server/WebHook.cpp | 4 ++-- src/Common/config.h | 4 ++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/api/include/mk_events.h b/api/include/mk_events.h index c628a0ec..0db68a5f 100644 --- a/api/include/mk_events.h +++ b/api/include/mk_events.h @@ -166,6 +166,17 @@ typedef struct { */ void (API_CALL *on_mk_log)(int level, const char *file, int line, const char *function, const char *message); + /** + * 发送rtp流失败回调,适用于mk_media_source_start_send_rtp/mk_media_start_send_rtp接口触发的rtp发送 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @param ssrc ssrc的10进制打印,通过atoi转换为整型 + * @param err 错误代码 + * @param msg 错误提示 + */ + void(API_CALL *on_mk_media_send_rtp_stop)(const char *vhost, const char *app, const char *stream, const char *ssrc, int err, const char *msg); + } mk_events; diff --git a/api/source/mk_events.cpp b/api/source/mk_events.cpp index 43e3362c..6e81ccc5 100644 --- a/api/source/mk_events.cpp +++ b/api/source/mk_events.cpp @@ -160,6 +160,13 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){ s_events.on_mk_log((int) ctx->_level, ctx->_file.data(), ctx->_line, ctx->_function.data(), log.data()); } }); + + NoticeCenter::Instance().addListener(&s_tag, Broadcast::kBroadcastSendRtpStopped,[](BroadcastSendRtpStoppedArgs){ + if (s_events.on_mk_media_send_rtp_stop) { + s_events.on_mk_media_send_rtp_stop(sender.getMediaTuple().vhost.c_str(), sender.getMediaTuple().app.c_str(), + sender.getMediaTuple().stream.c_str(), ssrc.c_str(), ex.getErrCode(), ex.what()); + } + }); }); } diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 424b8d1a..8eed641f 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -585,7 +585,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) { GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped); if (!hook_enable || hook_send_rtp_stopped.empty()) { return; @@ -656,7 +656,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) { GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout); if (!hook_enable || rtp_server_timeout.empty()) { return; diff --git a/src/Common/config.h b/src/Common/config.h index baafac2d..ec546a17 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader; // rtp推流被动停止时触发 extern const std::string kBroadcastSendRtpStopped; -#define BroadcastSendRtpStopped MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex +#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex // 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播 extern const std::string kBroadcastReloadConfig; @@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig; // rtp server 超时 extern const std::string KBroadcastRtpServerTimeout; -#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc +#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc #define ReloadConfigTag ((void *)(0xFF)) #define RELOAD_KEY(arg, key) \ From 9e8568e75360c2b85d6911560b6324588a22f37d Mon Sep 17 00:00:00 2001 From: yogo-zhangyingzhe <100331270+yogo-zhangyingzhe@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:28:34 +0800 Subject: [PATCH 20/92] =?UTF-8?q?=E6=B7=BB=E5=8A=A0rtp=20ext:=20av1-rtp-sp?= =?UTF-8?q?ec=20(#2609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: xia-chu <771730766@qq.com> --- webrtc/RtpExt.cpp | 10 +++++++--- webrtc/RtpExt.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index e1424ea7..dda65077 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -94,6 +94,7 @@ uint8_t RtpExtOneByte::getId() const { } void RtpExtOneByte::setId(uint8_t in) { + CHECK(in < (int)RtpExtType::reserved); id = in & 0x0F; } @@ -143,8 +144,6 @@ void appendExt(map &ret, uint8_t *ptr, const uint8_t *end) { ++ptr; continue; } - //15类型的rtp ext为保留 - CHECK(ext->getId() < (uint8_t) RtpExtType::reserved); CHECK(reinterpret_cast(ext) + Type::kMinSize <= end); CHECK(ext->getData() + ext->getSize() <= end); ret.emplace(ext->getId(), RtpExt(ext, isOneByteExt(), reinterpret_cast(ext->getData()), ext->getSize())); @@ -522,8 +521,13 @@ uint8_t RtpExt::getFramemarkingTID() const { } void RtpExt::setExtId(uint8_t ext_id) { - assert(ext_id > (int) RtpExtType::padding && ext_id <= (int) RtpExtType::reserved && _ext); + assert(ext_id > (int) RtpExtType::padding && _ext); if (_one_byte_ext) { + if (ext_id >= (int)RtpExtType::reserved) { + WarnL << "One byte rtp ext can not store id " << (int)ext_id << "(" << getExtName((RtpExtType)ext_id) << ") big than 14"; + clearExt(); + return; + } auto ptr = reinterpret_cast(_ext); ptr->setId(ext_id); } else { diff --git a/webrtc/RtpExt.h b/webrtc/RtpExt.h index 6345b394..524fa3bb 100644 --- a/webrtc/RtpExt.h +++ b/webrtc/RtpExt.h @@ -34,6 +34,7 @@ namespace mediakit { XX(playout_delay, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay") \ XX(video_orientation, "urn:3gpp:video-orientation") \ XX(toffset, "urn:ietf:params:rtp-hdrext:toffset") \ + XX(av1, "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension") \ XX(encrypt, "urn:ietf:params:rtp-hdrext:encrypt") enum class RtpExtType : uint8_t { @@ -41,7 +42,7 @@ enum class RtpExtType : uint8_t { #define XX(type, uri) type, RTP_EXT_MAP(XX) #undef XX - reserved = encrypt, + reserved = 15, }; class RtcMedia; From d41afa5d484d264b84d5d9ea33a9b5ed28fe9a39 Mon Sep 17 00:00:00 2001 From: johzzy Date: Sun, 9 Jul 2023 10:25:10 +0800 Subject: [PATCH 21/92] fix typo. (#2644) --- src/Http/HttpRequestSplitter.cpp | 2 +- src/Rtsp/RtpReceiver.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/HttpRequestSplitter.cpp b/src/Http/HttpRequestSplitter.cpp index 4d859686..a7883f2f 100644 --- a/src/Http/HttpRequestSplitter.cpp +++ b/src/Http/HttpRequestSplitter.cpp @@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) { _remain_data.assign(ptr, _remain_data_size); return; } - //收到content数据,并且接受content完毕 + //收到content数据,并且接收content完毕 onRecvContent(ptr,_content_len); _remain_data_size -= _content_len; diff --git a/src/Rtsp/RtpReceiver.cpp b/src/Rtsp/RtpReceiver.cpp index 363a3175..7e669138 100644 --- a/src/Rtsp/RtpReceiver.cpp +++ b/src/Rtsp/RtpReceiver.cpp @@ -71,7 +71,7 @@ RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr, } else { //ssrc错误 if (_ssrc_alive.elapsedTime() < 3 * 1000) { - //接受正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp + //接收正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc; return nullptr; } From 15d752d6ae81b05315d35faa8616ba8a608cfe36 Mon Sep 17 00:00:00 2001 From: johzzy Date: Sun, 9 Jul 2023 13:04:05 +0800 Subject: [PATCH 22/92] fix(www/webrtc): update 1080x720~1280x720 as default selected resolution in webrtc. (#2645) --- www/webrtc/index.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 78fb905b..555fc5b5 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -122,16 +122,16 @@ }; }); - ZLMRTCClient.GetAllScanResolution().forEach((r,i)=>{ + ZLMRTCClient.GetAllScanResolution().forEach((r,i) => { opt = document.createElement('option'); - opt.text = r.label +"("+r.width+"x"+r.height+")"; + opt.text = `${r.label}(${r.width}x${r.height})`; opt.value = r; + if (1080*720 <= r.width * r.height && r.width * r.height <= 1280*720) { + opt.selected = true; + } document.getElementById("resolution").add(opt,null) + }); - //console.log(opt.text.match(/\d+/g)) - - - }) function start_play(){ let elr = document.getElementById("resolution"); let res = elr.options[elr.selectedIndex].text.match(/\d+/g); From 8ee91d705b9419e6292ff4e70e915f5ce814846f Mon Sep 17 00:00:00 2001 From: johzzy Date: Sun, 9 Jul 2023 13:24:52 +0800 Subject: [PATCH 23/92] feat: support auto fill streamUrl by webrtc page(#2646) feat: support auto fill streamUrl by webrtc page, like "https://your-host/webrtc?app=live&stream=hello&type=push" --- www/webrtc/index.html | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 555fc5b5..269ae9b2 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -95,21 +95,28 @@ var ishttps = 'https:' == document.location.protocol ? true : false var isLocal = "file:" == document.location.protocol ? true : false - var url = document.location.protocol+"//"+window.location.host+"/index/api/webrtc?app=live&stream=test&type=play" + const searchParams = new URL(document.location.href).searchParams; + let type = searchParams.get('type'); + if (!['echo','push','play'].includes(type)) { + type = 'play'; + } + recvOnly = type === 'play'; + const apiPath = `/index/api/webrtc?app=${searchParams.get('app') ?? 'live'}&stream=${searchParams.get('stream') ?? 'test'}&type=${type}`; if(!ishttps && !isLocal){ alert('本demo需要在https的网站访问 ,如果你要推流的话(this demo must access in site of https if you want push stream)') } + + const apiHost = isLocal ? "http://127.0.0.1" : `${document.location.protocol}//${window.location.host}`; + var url = apiHost + apiPath; - if(isLocal){ - url = "http://127.0.0.1"+"/index/api/webrtc?app=live&stream=test&type=play" - } document.getElementById('streamUrl').value = url - document.getElementsByName("method").forEach((el,idx)=>{ - el.onclick=function(e){ + document.getElementsByName("method").forEach((el,idx) => { + el.checked = el.value === type; + el.onclick = function(e) { let url = new URL(document.getElementById('streamUrl').value); - url.searchParams.set("type",el.value) + url.searchParams.set("type",el.value); document.getElementById('streamUrl').value = url.toString() if(el.value == "play"){ @@ -275,7 +282,7 @@ } function fillStreamList(json) { clearStreamList(); - if (json.code != 0) { + if (json.code != 0 || !json.data) { return; } let ss = {}; From 4e33f5b4772d11953f4d8c598c48bbc2b6deaf51 Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Mon, 10 Jul 2023 10:53:02 +0800 Subject: [PATCH 24/92] rtsp player add query param for content-base (#2637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rtsp以content-base为基准,增加url的query参数(根据抓包分析,vlc是这样处理的),以兼容海康rtsp录像流与 直播流,主要是为了兼容这两个issue: #2624 #2501 --- src/Common/Parser.cpp | 1 - src/Rtsp/Rtsp.cpp | 183 ++++++++++++++++++------------ src/Rtsp/Rtsp.h | 239 +++++++++++++++++++--------------------- src/Rtsp/RtspPlayer.cpp | 26 ++--- src/Rtsp/RtspPlayer.h | 1 + 5 files changed, 240 insertions(+), 210 deletions(-) diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 6fa5aa2d..5ac280b9 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -320,7 +320,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { host = url.substr(0, pos); checkHost(host); } - #if 0 //测试代码 static onceToken token([](){ diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index 2e3618da..b2d2b86b 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -8,12 +8,12 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include -#include #include "Rtsp.h" #include "Common/Parser.h" #include "Common/config.h" #include "Network/Socket.h" +#include +#include using namespace std; using namespace toolkit; @@ -22,7 +22,8 @@ namespace mediakit { int RtpPayload::getClockRate(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return clock_rate; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return clock_rate; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 90000; @@ -31,7 +32,8 @@ int RtpPayload::getClockRate(int pt) { TrackType RtpPayload::getTrackType(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return type; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return type; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return TrackInvalid; @@ -40,7 +42,8 @@ TrackType RtpPayload::getTrackType(int pt) { int RtpPayload::getAudioChannel(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return channel; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return channel; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 1; @@ -49,7 +52,8 @@ int RtpPayload::getAudioChannel(int pt) { const char *RtpPayload::getName(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return "unknown payload type"; @@ -58,7 +62,8 @@ const char *RtpPayload::getName(int pt) { CodecId RtpPayload::getCodecId(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return codec_id; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return codec_id; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return CodecInvalid; @@ -85,7 +90,8 @@ static void getAttrSdp(const multimap &attr, _StrPrinter &printe string SdpTrack::getName() const { switch (_pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return _codec; @@ -94,7 +100,7 @@ string SdpTrack::getName() const { string SdpTrack::getControlUrl(const string &base_url) const { if (_control.find("://") != string::npos) { - //以rtsp://开头 + // 以rtsp://开头 return _control; } return base_url + "/" + _control; @@ -143,6 +149,7 @@ static TrackType toTrackType(const string &str) { } void SdpParser::load(const string &sdp) { + std::multimap global_infos; { _track_vec.clear(); SdpTrack::Ptr track = std::make_shared(); @@ -159,17 +166,23 @@ void SdpParser::load(const string &sdp) { string opt_val = line.substr(2); switch (opt) { case 't': - track->_t = opt_val; + if (_track_vec.empty()) + global_infos.emplace(opt, opt_val); + else + track->_t = opt_val; break; case 'b': - track->_b = opt_val; + if (_track_vec.empty()) + global_infos.emplace(opt, opt_val); + else + track->_b = opt_val; break; case 'm': { track = std::make_shared(); int pt, port, port_count; - char rtp[16] = {0}, type[16]; - if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt) || - 5 == sscanf(opt_val.data(), " %15[^ ] %d/%d %15[^ ] %d", type, &port, &port_count, rtp, &pt)) { + char rtp[16] = { 0 }, type[16]; + if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt) + || 5 == sscanf(opt_val.data(), " %15[^ ] %d/%d %15[^ ] %d", type, &port, &port_count, rtp, &pt)) { track->_pt = pt; track->_samplerate = RtpPayload::getClockRate(pt); track->_channel = RtpPayload::getAudioChannel(pt); @@ -182,29 +195,55 @@ void SdpParser::load(const string &sdp) { case 'a': { string attr = findSubString(opt_val.data(), nullptr, ":"); if (attr.empty()) { - track->_attr.emplace(opt_val, ""); + if (_track_vec.empty()) + global_infos.emplace(opt, opt_val); + else + track->_attr.emplace(opt_val, ""); } else { - track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); + if (_track_vec.empty()) + global_infos.emplace(opt, opt_val); + else + track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); + } + break; + } + default: { + if (_track_vec.empty()) { + global_infos.emplace(opt, opt_val); + } else { + track->_other[opt] = opt_val; } break; } - default: track->_other[opt] = opt_val; break; } } } + for (auto &info : global_infos) { + std::string attr; + switch (info.first) { + case 'a': + attr = findSubString(info.second.data(), nullptr, ":"); + if (attr == "control") { + _control_url = findSubString(info.second.data(), ":", nullptr); + } + break; + + default: break; + } + } for (auto &track_ptr : _track_vec) { auto &track = *track_ptr; auto it = track._attr.find("range"); if (it != track._attr.end()) { - char name[16] = {0}, start[16] = {0}, end[16] = {0}; + char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 }; int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); if (3 == ret || 2 == ret) { if (strcmp(start, "now") == 0) { strcpy(start, "0"); } - track._start = (float) atof(start); - track._end = (float) atof(end); + track._start = (float)atof(start); + track._end = (float)atof(end); track._duration = track._end - track._start; } } @@ -212,11 +251,11 @@ void SdpParser::load(const string &sdp) { for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { auto &rtpmap = it->second; int pt, samplerate, channel; - char codec[16] = {0}; + char codec[16] = { 0 }; sscanf(rtpmap.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -230,18 +269,18 @@ void SdpParser::load(const string &sdp) { track._samplerate = samplerate; } if (!track._samplerate && track._type == TrackVideo) { - //未设置视频采样率时,赋值为90000 + // 未设置视频采样率时,赋值为90000 track._samplerate = 90000; } ++it; } - for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) { + for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) { auto &fmtp = it->second; int pt; sscanf(fmtp.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -315,8 +354,16 @@ string SdpParser::toString() const { return title + video + audio; } -template -class PortManager : public std::enable_shared_from_this > { +std::string SdpParser::getControlUrl(const std::string &url) const { + if (_control_url.find("://") != string::npos) { + // 以rtsp://开头 + return _control_url; + } + return url; +} + +template +class PortManager : public std::enable_shared_from_this> { public: PortManager() { static auto func = [](const string &str, int index) { @@ -326,11 +373,11 @@ public: }; GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); }); GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); }); - assert(s_max_port >= s_min_port + 36 -1); + assert(s_max_port >= s_min_port + 36 - 1); setRange((s_min_port + 1) / 2, s_max_port / 2); } - static PortManager& Instance() { + static PortManager &Instance() { static auto instance = std::make_shared(); return *instance; } @@ -344,12 +391,12 @@ public: } if (is_udp) { if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[0] failed"); } if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[1] failed"); } @@ -359,12 +406,12 @@ public: sock1->setOnAccept(on_cycle); } else { if (!sock0->listen(2 * *sock_pair, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[0] failed"); } if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[1] failed"); } @@ -400,7 +447,7 @@ private: return; } InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1; - //回收端口号 + // 回收端口号 lock_guard lck(strong_self->_pool_mtx); strong_self->_port_pair_pool.emplace_back(pos); }); @@ -416,7 +463,7 @@ void makeSockPair(std::pair &pair, const string &local int try_count = 0; while (true) { try { - //udp和tcp端口池使用相同算法和范围分配,但是互不相干 + // udp和tcp端口池使用相同算法和范围分配,但是互不相干 if (is_udp) { PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp); } else { @@ -433,9 +480,9 @@ void makeSockPair(std::pair &pair, const string &local } string printSSRC(uint32_t ui32Ssrc) { - char tmp[9] = {0}; + char tmp[9] = { 0 }; ui32Ssrc = htonl(ui32Ssrc); - uint8_t *pSsrc = (uint8_t *) &ui32Ssrc; + uint8_t *pSsrc = (uint8_t *)&ui32Ssrc; for (int i = 0; i < 4; i++) { sprintf(tmp + 2 * i, "%02X", pSsrc[i]); } @@ -470,12 +517,10 @@ Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) { return rtp_tcp; } -#define AV_RB16(x) \ - ((((const uint8_t*)(x))[0] << 8) | \ - ((const uint8_t*)(x))[1]) +#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1]) size_t RtpHeader::getCsrcSize() const { - //每个csrc占用4字节 + // 每个csrc占用4字节 return csrc << 2; } @@ -487,18 +532,18 @@ uint8_t *RtpHeader::getCsrcData() { } size_t RtpHeader::getExtSize() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } auto ext_ptr = &payload + getCsrcSize(); - //uint16_t reserved = AV_RB16(ext_ptr); - //每个ext占用4字节 + // uint16_t reserved = AV_RB16(ext_ptr); + // 每个ext占用4字节 return AV_RB16(ext_ptr + 2) << 2; } uint16_t RtpHeader::getExtReserved() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } @@ -511,12 +556,12 @@ uint8_t *RtpHeader::getExtData() { return nullptr; } auto ext_ptr = &payload + getCsrcSize(); - //多出的4个字节分别为reserved、ext_len + // 多出的4个字节分别为reserved、ext_len return ext_ptr + 4; } size_t RtpHeader::getPayloadOffset() const { - //有ext时,还需要忽略reserved、ext_len 4个字节 + // 有ext时,还需要忽略reserved、ext_len 4个字节 return getCsrcSize() + (ext ? (4 + getExtSize()) : 0); } @@ -528,7 +573,7 @@ size_t RtpHeader::getPaddingSize(size_t rtp_size) const { if (!padding) { return 0; } - auto end = (uint8_t *) this + rtp_size - 1; + auto end = (uint8_t *)this + rtp_size - 1; return *end; } @@ -539,12 +584,12 @@ ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const { string RtpHeader::dumpString(size_t rtp_size) const { _StrPrinter printer; - printer << "version:" << (int) version << "\r\n"; + printer << "version:" << (int)version << "\r\n"; printer << "padding:" << getPaddingSize(rtp_size) << "\r\n"; printer << "ext:" << getExtSize() << "\r\n"; printer << "csrc:" << getCsrcSize() << "\r\n"; - printer << "mark:" << (int) mark << "\r\n"; - printer << "pt:" << (int) pt << "\r\n"; + printer << "mark:" << (int)mark << "\r\n"; + printer << "pt:" << (int)pt << "\r\n"; printer << "seq:" << ntohs(seq) << "\r\n"; printer << "stamp:" << ntohl(stamp) << "\r\n"; printer << "ssrc:" << ntohl(ssrc) << "\r\n"; @@ -557,16 +602,16 @@ string RtpHeader::dumpString(size_t rtp_size) const { /////////////////////////////////////////////////////////////////////// RtpHeader *RtpPacket::getHeader() { - //需除去rtcp over tcp 4个字节长度 - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + // 需除去rtcp over tcp 4个字节长度 + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } const RtpHeader *RtpPacket::getHeader() const { - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } string RtpPacket::dumpString() const { - return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); + return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); } uint16_t RtpPacket::getSeq() const { @@ -590,7 +635,7 @@ uint8_t *RtpPacket::getPayload() { } size_t RtpPacket::getPayloadSize() const { - //需除去rtcp over tcp 4个字节长度 + // 需除去rtcp over tcp 4个字节长度 return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize); } @@ -608,23 +653,22 @@ RtpPacket::Ptr RtpPacket::create() { #endif } - /** -* 构造title类型sdp -* @param dur_sec rtsp点播时长,0代表直播,单位秒 -* @param header 自定义sdp描述 -* @param version sdp版本 -*/ + * 构造title类型sdp + * @param dur_sec rtsp点播时长,0代表直播,单位秒 + * @param header 自定义sdp描述 + * @param version sdp版本 + */ -TitleSdp::TitleSdp(float dur_sec, const std::map& header, int version) : Sdp(0, 0) { +TitleSdp::TitleSdp(float dur_sec, const std::map &header, int version) + : Sdp(0, 0) { _printer << "v=" << version << "\r\n"; if (!header.empty()) { for (auto &pr : header) { _printer << pr.first << "=" << pr.second << "\r\n"; } - } - else { + } else { _printer << "o=- 0 0 IN IP4 0.0.0.0\r\n"; _printer << "s=Streamed by " << kServerName << "\r\n"; _printer << "c=IN IP4 0.0.0.0\r\n"; @@ -632,18 +676,17 @@ TitleSdp::TitleSdp(float dur_sec, const std::map& head } if (dur_sec <= 0) { - //直播 + // 直播 _printer << "a=range:npt=now-\r\n"; - } - else { - //点播 + } else { + // 点播 _dur_sec = dur_sec; _printer << "a=range:npt=0-" << dur_sec << "\r\n"; } _printer << "a=control:*\r\n"; } -}//namespace mediakit +} // namespace mediakit namespace toolkit { StatisticImp(mediakit::RtpPacket); diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 6bb19d8d..55e8ee5a 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -11,13 +11,13 @@ #ifndef RTSP_RTSP_H_ #define RTSP_RTSP_H_ -#include -#include -#include -#include #include "Common/macros.h" #include "Extension/Frame.h" #include "Network/Socket.h" +#include +#include +#include +#include namespace mediakit { @@ -29,40 +29,41 @@ typedef enum { RTP_MULTICAST = 2, } eRtpType; -#define RTP_PT_MAP(XX) \ - XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ - XX(GSM, TrackAudio , 3, 8000, 1, CodecInvalid) \ - XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ - XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ - XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ - XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ - XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ - XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ - XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ - XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ - XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ - XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ - XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ - XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ - XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ - XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ - XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ - XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ - XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ - XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ - XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ - XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ - XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ - XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) \ +#define RTP_PT_MAP(XX) \ + XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ + XX(GSM, TrackAudio, 3, 8000, 1, CodecInvalid) \ + XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ + XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ + XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ + XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ + XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ + XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ + XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ + XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ + XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ + XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ + XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ + XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ + XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ + XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ + XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ + XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ + XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ + XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ + XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ + XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ + XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ + XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) typedef enum { -#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_ ## name = value, +#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_##name = value, RTP_PT_MAP(ENUM_DEF) #undef ENUM_DEF - PT_MAX = 128 + PT_MAX + = 128 } PayloadType; -}; +}; // namespace Rtsp #if defined(_WIN32) #pragma pack(push, 1) @@ -71,65 +72,65 @@ typedef enum { class RtpHeader { public: #if __BYTE_ORDER == __BIG_ENDIAN - //版本号,固定为2 - uint32_t version: 2; - //padding - uint32_t padding: 1; - //扩展 - uint32_t ext: 1; - //csrc - uint32_t csrc: 4; - //mark - uint32_t mark: 1; - //负载类型 - uint32_t pt: 7; + // 版本号,固定为2 + uint32_t version : 2; + // padding + uint32_t padding : 1; + // 扩展 + uint32_t ext : 1; + // csrc + uint32_t csrc : 4; + // mark + uint32_t mark : 1; + // 负载类型 + uint32_t pt : 7; #else - //csrc - uint32_t csrc: 4; - //扩展 - uint32_t ext: 1; - //padding - uint32_t padding: 1; - //版本号,固定为2 - uint32_t version: 2; - //负载类型 - uint32_t pt: 7; - //mark - uint32_t mark: 1; + // csrc + uint32_t csrc : 4; + // 扩展 + uint32_t ext : 1; + // padding + uint32_t padding : 1; + // 版本号,固定为2 + uint32_t version : 2; + // 负载类型 + uint32_t pt : 7; + // mark + uint32_t mark : 1; #endif - //序列号 - uint32_t seq: 16; - //时间戳 + // 序列号 + uint32_t seq : 16; + // 时间戳 uint32_t stamp; - //ssrc + // ssrc uint32_t ssrc; - //负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) + // 负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) uint8_t payload; public: - //返回csrc字段字节长度 + // 返回csrc字段字节长度 size_t getCsrcSize() const; - //返回csrc字段首地址,不存在时返回nullptr + // 返回csrc字段首地址,不存在时返回nullptr uint8_t *getCsrcData(); - //返回ext字段字节长度 + // 返回ext字段字节长度 size_t getExtSize() const; - //返回ext reserved值 + // 返回ext reserved值 uint16_t getExtReserved() const; - //返回ext段首地址,不存在时返回nullptr + // 返回ext段首地址,不存在时返回nullptr uint8_t *getExtData(); - //返回有效负载指针,跳过csrc、ext - uint8_t* getPayloadData(); - //返回有效负载总长度,不包括csrc、ext、padding + // 返回有效负载指针,跳过csrc、ext + uint8_t *getPayloadData(); + // 返回有效负载总长度,不包括csrc、ext、padding ssize_t getPayloadSize(size_t rtp_size) const; - //打印调试信息 + // 打印调试信息 std::string dumpString(size_t rtp_size) const; private: - //返回有效负载偏移量 + // 返回有效负载偏移量 size_t getPayloadOffset() const; - //返回padding长度 + // 返回padding长度 size_t getPaddingSize(size_t rtp_size) const; } PACKED; @@ -137,40 +138,36 @@ private: #pragma pack(pop) #endif // defined(_WIN32) -//此rtp为rtp over tcp形式,需要忽略前4个字节 -class RtpPacket : public toolkit::BufferRaw{ +// 此rtp为rtp over tcp形式,需要忽略前4个字节 +class RtpPacket : public toolkit::BufferRaw { public: using Ptr = std::shared_ptr; - enum { - kRtpVersion = 2, - kRtpHeaderSize = 12, - kRtpTcpHeaderSize = 4 - }; + enum { kRtpVersion = 2, kRtpHeaderSize = 12, kRtpTcpHeaderSize = 4 }; - //获取rtp头 - RtpHeader* getHeader(); - const RtpHeader* getHeader() const; + // 获取rtp头 + RtpHeader *getHeader(); + const RtpHeader *getHeader() const; - //打印调试信息 + // 打印调试信息 std::string dumpString() const; - //主机字节序的seq + // 主机字节序的seq uint16_t getSeq() const; uint32_t getStamp() const; - //主机字节序的时间戳,已经转换为毫秒 + // 主机字节序的时间戳,已经转换为毫秒 uint64_t getStampMS(bool ntp = true) const; - //主机字节序的ssrc + // 主机字节序的ssrc uint32_t getSSRC() const; - //有效负载,跳过csrc、ext - uint8_t* getPayload(); - //有效负载长度,不包括csrc、ext、padding + // 有效负载,跳过csrc、ext + uint8_t *getPayload(); + // 有效负载长度,不包括csrc、ext、padding size_t getPayloadSize() const; - //音视频类型 + // 音视频类型 TrackType type; - //音频为采样率,视频一般为90000 + // 音频为采样率,视频一般为90000 uint32_t sample_rate; - //ntp时间戳 + // ntp时间戳 uint64_t ntp_stamp; static Ptr create(); @@ -180,7 +177,7 @@ private: RtpPacket() = default; private: - //对象个数统计 + // 对象个数统计 toolkit::ObjectStatistic _statistic; }; @@ -229,7 +226,7 @@ public: uint8_t _interleaved = 0; uint16_t _seq = 0; uint32_t _ssrc = 0; - //时间戳,单位毫秒 + // 时间戳,单位毫秒 uint32_t _time_stamp = 0; }; @@ -246,15 +243,17 @@ public: SdpTrack::Ptr getTrack(TrackType type) const; std::vector getAvailableTrack() const; std::string toString() const; + std::string getControlUrl(const std::string &url) const; private: std::vector _track_vec; + std::string _control_url; }; /** -* rtsp sdp基类 -*/ -class Sdp : public CodecInfo{ + * rtsp sdp基类 + */ +class Sdp : public CodecInfo { public: using Ptr = std::shared_ptr; @@ -263,7 +262,7 @@ public: * @param sample_rate 采样率 * @param payload_type pt类型 */ - Sdp(uint32_t sample_rate, uint8_t payload_type){ + Sdp(uint32_t sample_rate, uint8_t payload_type) { _sample_rate = sample_rate; _payload_type = payload_type; } @@ -274,23 +273,19 @@ public: * 获取sdp字符串 * @return */ - virtual std::string getSdp() const = 0; + virtual std::string getSdp() const = 0; /** * 获取pt * @return */ - uint8_t getPayloadType() const{ - return _payload_type; - } + uint8_t getPayloadType() const { return _payload_type; } /** * 获取采样率 * @return */ - uint32_t getSampleRate() const{ - return _sample_rate; - } + uint32_t getSampleRate() const { return _sample_rate; } private: uint8_t _payload_type; @@ -298,9 +293,9 @@ private: }; /** -* sdp中除音视频外的其他描述部分 -*/ -class TitleSdp : public Sdp{ + * sdp中除音视频外的其他描述部分 + */ +class TitleSdp : public Sdp { public: using Ptr = std::shared_ptr; /** @@ -309,36 +304,28 @@ public: * @param header 自定义sdp描述 * @param version sdp版本 */ - TitleSdp(float dur_sec = 0, - const std::map &header = std::map(), - int version = 0); + TitleSdp(float dur_sec = 0, const std::map &header = std::map(), int version = 0); - std::string getSdp() const override { - return _printer; - } + std::string getSdp() const override { return _printer; } - CodecId getCodecId() const override { - return CodecInvalid; - } + CodecId getCodecId() const override { return CodecInvalid; } - float getDuration() const { - return _dur_sec; - } + float getDuration() const { return _dur_sec; } private: float _dur_sec = 0; toolkit::_StrPrinter _printer; }; -//创建rtp over tcp4个字节的头 +// 创建rtp over tcp4个字节的头 toolkit::Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved); -//创建rtp-rtcp端口对 +// 创建rtp-rtcp端口对 void makeSockPair(std::pair &pair, const std::string &local_ip, bool re_use_port = false, bool is_udp = true); -//十六进制方式打印ssrc +// 十六进制方式打印ssrc std::string printSSRC(uint32_t ui32Ssrc); bool isRtp(const char *buf, size_t size); -bool isRtcp(const char *buf, size_t size); +bool isRtcp(const char *buf, size_t size); -} //namespace mediakit -#endif //RTSP_RTSP_H_ +} // namespace mediakit +#endif // RTSP_RTSP_H_ diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 0dafa03a..599677ec 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -38,8 +38,8 @@ RtspPlayer::~RtspPlayer(void) { void RtspPlayer::sendTeardown() { if (alive()) { - if (!_content_base.empty()) { - sendRtspRequest("TEARDOWN", _content_base); + if (!_control_url.empty()) { + sendRtspRequest("TEARDOWN", _control_url); } shutdown(SockException(Err_shutdown, "teardown")); } @@ -203,6 +203,8 @@ void RtspPlayer::handleResDESCRIBE(const Parser &parser) { // 解析sdp SdpParser sdpParser(parser.content()); + _control_url = sdpParser.getControlUrl(_content_base); + string sdp; auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); if (play_track != TrackInvalid) { @@ -412,7 +414,7 @@ void RtspPlayer::sendKeepAlive() { _on_response = [](const Parser &parser) {}; if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) { // 支持GET_PARAMETER,用此命令保活 - sendRtspRequest("GET_PARAMETER", _content_base); + sendRtspRequest("GET_PARAMETER", _control_url); } else { // 不支持GET_PARAMETER,用OPTIONS命令保活 sendRtspRequest("OPTIONS", _play_url); @@ -423,12 +425,12 @@ void RtspPlayer::sendPause(int type, uint32_t seekMS) { _on_response = std::bind(&RtspPlayer::handleResPAUSE, this, placeholders::_1, type); // 开启或暂停rtsp switch (type) { - case type_pause: sendRtspRequest("PAUSE", _content_base); break; + case type_pause: sendRtspRequest("PAUSE", _control_url, {}); break; case type_play: // sendRtspRequest("PLAY", _content_base); // break; case type_seek: - sendRtspRequest("PLAY", _content_base, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" }); + sendRtspRequest("PLAY", _control_url, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" }); break; default: WarnL << "unknown type : " << type; @@ -442,7 +444,7 @@ void RtspPlayer::pause(bool bPause) { } void RtspPlayer::speed(float speed) { - sendRtspRequest("PLAY", _content_base, { "Scale", StrPrinter << speed }); + sendRtspRequest("PLAY", _control_url, { "Scale", StrPrinter << speed }); } void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { @@ -451,7 +453,7 @@ void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { case type_pause: WarnL << "Pause failed:" << parser.status() << " " << parser.statusStr(); break; case type_play: WarnL << "Play failed:" << parser.status() << " " << parser.statusStr(); - onPlayResult_l(SockException(Err_shutdown, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); + onPlayResult_l(SockException(Err_other, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); break; case type_seek: WarnL << "Seek failed:" << parser.status() << " " << parser.statusStr(); break; } @@ -571,6 +573,7 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std key = val; } } + sendRtspRequest(cmd, url, header_map); } @@ -615,12 +618,9 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const Str } _StrPrinter printer; - if (cmd == "PLAY") { - printer << cmd << " " << _play_url << " RTSP/1.0\r\n"; - } else { - printer << cmd << " " << url << " RTSP/1.0\r\n"; - } + printer << cmd << " " << url << " RTSP/1.0\r\n"; + TraceL << cmd << " "<< url; for (auto &pr : header) { printer << pr.first << ": " << pr.second << "\r\n"; } @@ -743,7 +743,7 @@ int RtspPlayer::getTrackIndexByTrackType(TrackType track_type) const { if (_sdp_track.size() == 1) { return 0; } - throw SockException(Err_shutdown, StrPrinter << "no such track with type:" << getTrackString(track_type)); + throw SockException(Err_other, StrPrinter << "no such track with type:" << getTrackString(track_type)); } /////////////////////////////////////////////////// diff --git a/src/Rtsp/RtspPlayer.h b/src/Rtsp/RtspPlayer.h index 5a9bd1ee..2b4f34c5 100644 --- a/src/Rtsp/RtspPlayer.h +++ b/src/Rtsp/RtspPlayer.h @@ -129,6 +129,7 @@ private: std::string _session_id; uint32_t _cseq_send = 1; std::string _content_base; + std::string _control_url; Rtsp::eRtpType _rtp_type = Rtsp::RTP_TCP; //当前rtp时间戳 From 7b1a4d23e15524d47a85fc0ff588299d4f464e74 Mon Sep 17 00:00:00 2001 From: Xiaofeng Wang Date: Sat, 15 Jul 2023 18:47:05 +0800 Subject: [PATCH 25/92] Reset cached value at start (#2664) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 780adb4a..841e83da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,8 +201,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") endif() # mediakit 以及各个 runtime 依赖 -update_cached_list(MK_LINK_LIBRARIES "") -update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION) +update_cached(MK_LINK_LIBRARIES "") +update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION) if (DISABLE_REPORT) update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT) From 75e41bf3b8ad76c4a92cd88eab272d3a6788f30c Mon Sep 17 00:00:00 2001 From: Xiaofeng Wang Date: Wed, 19 Jul 2023 22:38:28 +0800 Subject: [PATCH 26/92] =?UTF-8?q?=E4=BB=85=E5=9C=A8=E6=9C=89=20parent=20?= =?UTF-8?q?=E6=97=B6=E8=AE=BE=E7=BD=AE=E5=88=B0=20parent=20=E4=B8=AD=20(#2?= =?UTF-8?q?676)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 841e83da..cb85679b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -462,7 +462,7 @@ if(ENABLE_SERVER) endif() # Android 会 add_subdirectory 并依赖该变量 -if(ENABLE_SERVER_LIB) +if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE) set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE) endif() From a31108cc5102ebd515f5b0622d2012b6c16338c7 Mon Sep 17 00:00:00 2001 From: Xiaofeng Wang Date: Wed, 19 Jul 2023 23:36:03 +0800 Subject: [PATCH 27/92] =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E5=BA=93=E5=88=97=E8=A1=A8=E5=AF=BC=E8=87=B4=20test=20?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=A4=B1=E8=B4=A5=20(#2677)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 748cb48a..63abc05d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -34,7 +34,7 @@ if(ENABLE_SERVER_LIB) PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_link_libraries(MediaServer PRIVATE ${MK_LINK_LIBRARIES}) - update_cached(MK_LINK_LIBRARIES MediaServer) + update_cached_list(MK_LINK_LIBRARIES MediaServer) return() endif() From 19d6f1a0037cfdc1752c60fe5a9a8bb6c0b4171d Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Thu, 20 Jul 2023 17:13:26 +0800 Subject: [PATCH 28/92] FLV player avoid script data first element not string (#2679 #2680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 兼容不规范的flv流,比如livegbs产生的flv流metadata flv tag解析异常 --- src/Rtmp/FlvSplitter.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Rtmp/FlvSplitter.cpp b/src/Rtmp/FlvSplitter.cpp index c2e21e6a..3e99d9ef 100644 --- a/src/Rtmp/FlvSplitter.cpp +++ b/src/Rtmp/FlvSplitter.cpp @@ -91,21 +91,26 @@ void FlvSplitter::onRecvContent(const char *data, size_t len) { case MSG_DATA3: { BufferLikeString buffer(string(data, len)); AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); - std::string type = dec.load(); + auto first = dec.load(); bool flag = true; - if (type == "@setDataFrame") { - std::string type = dec.load(); - if (type == "onMetaData") { + if (first.type() == AMFType::AMF_STRING) { + auto type = first.as_string(); + if (type == "@setDataFrame") { + type = dec.load(); + if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown type:" << type; + } + } else if (type == "onMetaData") { flag = onRecvMetadata(dec.load()); } else { - WarnL << "unknown type:" << type; + WarnL << "unknown notify:" << type; } - } else if (type == "onMetaData") { - flag = onRecvMetadata(dec.load()); } else { - WarnL << "unknown notify:" << type; + WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string(); } - if(!flag){ + if (!flag) { throw std::invalid_argument("check rtmp metadata failed"); } return; From eb382c3fc917f046445a2bc13704356c893f0888 Mon Sep 17 00:00:00 2001 From: Dw9 Date: Sat, 22 Jul 2023 10:46:17 +0800 Subject: [PATCH 29/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwhip=20delete=5Fwebrtc?= =?UTF-8?q?=E6=9C=AA=E6=B3=A8=E9=94=80=E6=B5=81=20(#2688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bug触发条件: 1、obs whip 推流到zlm 2、关闭推流 3、zlm 未注销流 --- webrtc/WebRtcPusher.cpp | 9 +++++++-- webrtc/WebRtcPusher.h | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 5d6a68d2..11fb1b30 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -149,7 +149,7 @@ void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { configure.audio.direction = configure.video.direction = RtpDirection::recvonly; } -float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type){ +float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) { return WebRtcTransportImp::getLossRate(type); } @@ -159,8 +159,13 @@ void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); } -void WebRtcPusher::onRtcpBye(){ +void WebRtcPusher::onRtcpBye() { WebRtcTransportImp::onRtcpBye(); } +void WebRtcPusher::onShutdown(const SockException &ex) { + _push_src = nullptr; + WebRtcTransportImp::onShutdown(ex); +} + }// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index b35f9e98..4c332a35 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -23,15 +23,17 @@ public: static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, bool preferred_tcp = false); + protected: ///////WebRtcTransportImp override/////// void onStartWebRTC() override; void onDestory() override; void onRtcConfigure(RtcConfigure &configure) const override; void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + void onShutdown(const SockException &ex) override; void onRtcpBye() override; //// dtls相关的回调 //// - void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; + void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; protected: ///////MediaSourceEvent override/////// From a17e842da26efa6b9a45c28282887d2e012647e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:29:09 +0800 Subject: [PATCH 30/92] =?UTF-8?q?=E5=85=BC=E5=AE=B9ffmpeg6.0=20(#2648=20#2?= =?UTF-8?q?689)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Codec/Transcode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Codec/Transcode.cpp b/src/Codec/Transcode.cpp index bab515cc..25f9e77c 100644 --- a/src/Codec/Transcode.cpp +++ b/src/Codec/Transcode.cpp @@ -436,6 +436,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: av_dict_set(&dict, "zerolatency", "1", 0); av_dict_set(&dict, "strict", "-2", 0); +#ifdef AV_CODEC_CAP_TRUNCATED if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) { /* we do not send complete frames */ _context->flags |= AV_CODEC_FLAG_TRUNCATED; @@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: // 此时业务层应该需要合帧 _do_merger = true; } +#endif int ret = avcodec_open2(_context.get(), codec, &dict); av_dict_free(&dict); From 25d5e410c3b68d7b73e0a7c4f8db840d2e1630bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:30:20 +0800 Subject: [PATCH 31/92] =?UTF-8?q?=E7=A6=81=E6=AD=A2=E9=BB=98=E8=AE=A4http?= =?UTF-8?q?=20api=E5=AF=86=E9=92=A5=EF=BC=8C=E5=88=A0=E9=99=A4=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E8=B6=8A=E8=BF=87=E9=89=B4=E6=9D=83=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20(#2655=20#2690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit http api secret禁止使用默认的035c73f7-bb6b-4889-a715-d9eb2d1925cc 删除admin_params的设定 --- conf/config.ini | 5 ----- server/WebApi.h | 2 ++ server/WebHook.cpp | 13 +++++-------- server/main.cpp | 8 ++++++-- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index c6a7e545..f3c51015 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -142,9 +142,6 @@ deleteDelaySec=10 segKeep=0 [hook] -#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 -#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 -admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=0 #播放器或推流器使用流量事件,置空则关闭 @@ -273,8 +270,6 @@ handshakeSecond=15 #rtmp超时时间,如果该时间内未收到客户端的数据, #或者tcp发送缓存超过这个时间,则会断开连接,单位秒 keepAliveSecond=15 -#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) -modifyStamp=0 #rtmp服务器监听端口 port=1935 #rtmps服务器监听地址 diff --git a/server/WebApi.h b/server/WebApi.h index 15210d53..d4d557f4 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -44,6 +44,8 @@ typedef enum { OtherFailed = -1,//业务代码执行失败, Success = 0//执行成功 } ApiErr; + +extern const std::string kSecret; }//namespace API class ApiRetException: public std::runtime_error { diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 8eed641f..c185444b 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -48,7 +48,6 @@ const string kOnServerExited = HOOK_FIELD "on_server_exited"; const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive"; const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped"; const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout"; -const string kAdminParams = HOOK_FIELD "admin_params"; const string kAliveInterval = HOOK_FIELD "alive_interval"; const string kRetry = HOOK_FIELD "retry"; const string kRetryDelay = HOOK_FIELD "retry_delay"; @@ -74,7 +73,6 @@ static onceToken token([]() { mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnSendRtpStopped] = ""; mINI::Instance()[kOnRtpServerTimeout] = ""; - mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; mINI::Instance()[kRetryDelay] = 3.0; @@ -331,11 +329,10 @@ static mINI jsonToMini(const Value &obj) { void installWebHook() { GET_CONFIG(bool, hook_enable, Hook::kEnable); - GET_CONFIG(string, hook_adminparams, Hook::kAdminParams); NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { GET_CONFIG(string, hook_publish, Hook::kOnPublish); - if (!hook_enable || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") { invoker("", ProtocolOption()); return; } @@ -360,7 +357,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { GET_CONFIG(string, hook_play, Hook::kOnPlay); - if (!hook_enable || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") { invoker(""); return; } @@ -374,7 +371,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport); - if (!hook_enable || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") { return; } auto body = make_json(args); @@ -393,7 +390,7 @@ void installWebHook() { // 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm); - if (!hook_enable || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") { // 无需认证 invoker(""); return; @@ -620,7 +617,7 @@ void installWebHook() { // 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) { GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess); - if (sender.get_peer_ip() == "127.0.0.1" || parser.params() == hook_adminparams) { + if (sender.get_peer_ip() == "127.0.0.1") { // 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 invoker("", "", 60 * 60); return; diff --git a/server/main.cpp b/server/main.cpp index d09bf5d5..b9d84249 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -351,6 +351,11 @@ int start_main(int argc,char *argv[]) { #endif //defined(ENABLE_SRT) try { + auto secret = mINI::Instance()[API::kSecret]; + if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) { + // 使用默认secret被禁止启动 + throw std::invalid_argument("please modify the configuration named " + API::kSecret + " in " + g_ini_file); + } //rtsp服务器,端口默认554 if (rtspPort) { rtspSrv->start(rtspPort); } //rtsps服务器,端口默认322 @@ -388,8 +393,7 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { - WarnL << "端口占用或无权限:" << ex.what(); - ErrorL << "程序启动失败,请修改配置文件中端口号后重试!"; + ErrorL << "Start server failed: " << ex.what(); sleep(1); #if !defined(_WIN32) if (pid != getpid() && kill_parent_if_failed) { From 09aa38334e84b96f9585df29b5ab94c42ad24580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:30:39 +0800 Subject: [PATCH 32/92] =?UTF-8?q?H264=20rtsp=20sdp=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E7=9A=84packetization-mode=20(#2691)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Extension/H264.cpp | 12 ++++++++++-- webrtc/Sdp.cpp | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Extension/H264.cpp b/src/Extension/H264.cpp index 2395159b..eeccd600 100644 --- a/src/Extension/H264.cpp +++ b/src/Extension/H264.cpp @@ -12,9 +12,10 @@ #include "SPSParser.h" #include "Util/logger.h" #include "Util/base64.h" +#include "Common/config.h" -using namespace toolkit; using namespace std; +using namespace toolkit; namespace mediakit { @@ -248,7 +249,14 @@ public: _printer << "b=AS:" << bitrate << "\r\n"; } _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n"; - _printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id="; + + /** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + **/ + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + _printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id="; uint32_t profile_level_id = 0; if (strSPS.length() >= 4) { // sanity check diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index 1b08ca8d..7b2e06e9 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -1816,11 +1816,17 @@ bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) return true; } +/** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + **/ void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const { if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { - //h264时,设置packetization-mod为一致 + // h264时,设置packetization-mod为一致 auto mode = _rtsp_video_plan->fmtp[kMode]; - plan.fmtp[kMode] = mode.empty() ? "0" : mode; + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + plan.fmtp[kMode] = mode.empty() ? std::to_string(h264_stap_a) : mode; } } From a97f1e503d1141492e90a1c327ce59327261c2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:31:02 +0800 Subject: [PATCH 33/92] =?UTF-8?q?=E6=8F=90=E9=AB=98http/rtsp=20header?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=85=BC=E5=AE=B9=E6=80=A7=20(#2670=20#2693)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/Parser.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 5ac280b9..9c073f2c 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -48,8 +48,13 @@ void Parser::parse(const char *buf, size_t size) { clear(); auto ptr = buf; while (true) { - auto next_line = strstr(ptr, "\r\n"); - CHECK(next_line); + auto next_line = strchr(ptr, '\n'); + auto offset = 1; + CHECK(next_line && next_line > ptr); + if (*(next_line - 1) == '\r') { + next_line -= 1; + offset = 2; + } if (ptr == buf) { auto blank = strchr(ptr, ' '); CHECK(blank > ptr && blank < next_line); @@ -76,7 +81,7 @@ void Parser::parse(const char *buf, size_t size) { } _headers.emplace_force(trim(std::move(key)), trim(std::move(value))); } - ptr = next_line + 2; + ptr = next_line + offset; if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕 _content.assign(ptr + 2, buf + size); break; From b44ca8fd6f6a16e61dd5513d01e607bc1b8e6411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:31:23 +0800 Subject: [PATCH 34/92] =?UTF-8?q?rtmp=E5=8D=8F=E8=AE=AE=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0metadata=20(#2669=20#2692)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtmp/FlvMuxer.cpp | 8 +-- src/Rtmp/RtmpMediaSource.h | 31 +++------ src/Rtmp/RtmpMediaSourceImp.cpp | 114 +++++++++++++++++--------------- src/Rtmp/RtmpPusher.cpp | 21 ++++-- src/Rtmp/RtmpSession.cpp | 13 ++-- 5 files changed, 97 insertions(+), 90 deletions(-) diff --git a/src/Rtmp/FlvMuxer.cpp b/src/Rtmp/FlvMuxer.cpp index a68df5bc..bee86ddb 100644 --- a/src/Rtmp/FlvMuxer.cpp +++ b/src/Rtmp/FlvMuxer.cpp @@ -107,14 +107,12 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { //flv header onWrite(buffer, false); - auto &metadata = src->getMetaData(); - if (metadata) { - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata + // metadata + src->getMetaData([&](const AMFValue &metadata) { AMFEncoder invoke; invoke << "onMetaData" << metadata; onWriteFlvTag(MSG_DATA, std::make_shared(invoke.data()), 0, false); - } + }); //config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 5c862a36..718f6055 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -73,42 +73,29 @@ public: /** * 获取metadata */ - const AMFValue &getMetaData() const { + template + void getMetaData(const FUNC &func) const { std::lock_guard lock(_mtx); - return _metadata; + if (_metadata) { + func(_metadata); + } } /** * 获取所有的config帧 */ - template - void getConfigFrame(const FUNC &f) { + template + void getConfigFrame(const FUNC &func) { std::lock_guard lock(_mtx); for (auto &pr : _config_frame_map) { - f(pr.second); + func(pr.second); } } /** * 设置metadata */ - virtual void setMetaData(const AMFValue &metadata) { - _metadata = metadata; - _metadata.set("title", std::string("Streamed by ") + kServerName); - _have_video = _metadata["videocodecid"]; - _have_audio = _metadata["audiocodecid"]; - if (_ring) { - regist(); - } - } - - /** - * 更新metadata - */ - void updateMetaData(const AMFValue &metadata) { - std::lock_guard lock(_mtx); - _metadata = metadata; - } + virtual void setMetaData(const AMFValue &metadata); /** * 输入rtmp包 diff --git a/src/Rtmp/RtmpMediaSourceImp.cpp b/src/Rtmp/RtmpMediaSourceImp.cpp index ce64226a..ee072b8e 100644 --- a/src/Rtmp/RtmpMediaSourceImp.cpp +++ b/src/Rtmp/RtmpMediaSourceImp.cpp @@ -2,15 +2,15 @@ #include "RtmpMediaSourceImp.h" namespace mediakit { -uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) -{ + +uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) { assert(trackType >= TrackInvalid && trackType < TrackMax); if (trackType != TrackInvalid) { - //获取某track的时间戳 + // 获取某track的时间戳 return _track_stamps[trackType]; } - //获取所有track的最小时间戳 + // 获取所有track的最小时间戳 uint32_t ret = UINT32_MAX; for (auto &stamp : _track_stamps) { if (stamp > 0 && stamp < ret) { @@ -20,38 +20,61 @@ uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) return ret; } -void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) -{ +void RtmpMediaSource::setMetaData(const AMFValue &metadata) { + { + std::lock_guard lock(_mtx); + _metadata = metadata; + _metadata.set("title", std::string("Streamed by ") + kServerName); + } + + _have_video = _metadata["videocodecid"]; + _have_audio = _metadata["audiocodecid"]; + if (_ring) { + regist(); + + AMFEncoder enc; + enc << "onMetaData" << _metadata; + RtmpPacket::Ptr packet = RtmpPacket::create(); + packet->buffer = enc.data(); + packet->type_id = MSG_DATA; + packet->time_stamp = 0; + packet->chunk_id = CHUNK_CLIENT_REQUEST_AFTER; + packet->stream_index = STREAM_MEDIA; + onWrite(std::move(packet)); + } +} + +void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { bool is_video = pkt->type_id == MSG_VIDEO; _speed[is_video ? TrackVideo : TrackAudio] += pkt->size(); - //保存当前时间戳 + // 保存当前时间戳 switch (pkt->type_id) { - case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break; - case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break; - default: break; + case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break; + case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break; + default: break; } if (pkt->isCfgFrame()) { std::lock_guard lock(_mtx); _config_frame_map[pkt->type_id] = pkt; if (!_ring) { - //注册后收到config帧更新到各播放器 + // 注册后收到config帧更新到各播放器 return; } } if (!_ring) { - std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); - auto lam = [weakSelf](int size) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + auto lam = [weak_self](int size) { + auto strong_self = weak_self.lock(); + if (!strong_self) { return; } - strongSelf->onReaderChanged(size); + strong_self->onReaderChanged(size); }; - //GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包), - //每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) + // GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包), + // 每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) _ring = std::make_shared(_ring_size, std::move(lam)); if (_metadata) { regist(); @@ -62,47 +85,42 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) PacketCache::inputPacket(stamp, is_video, std::move(pkt), key); } - -RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize) -{ +RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple &tuple, int ringSize) + : RtmpMediaSource(tuple, ringSize) { _demuxer = std::make_shared(); _demuxer->setTrackListener(this); } -void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) -{ +void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) { if (!_demuxer->loadMetaData(metadata)) { - //该metadata无效,需要重新生成 + // 该metadata无效,需要重新生成 _metadata = metadata; _recreate_metadata = true; } RtmpMediaSource::setMetaData(metadata); } -void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) -{ +void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { if (!_all_track_ready || _muxer->isEnabled()) { - //未获取到所有Track后,或者开启转协议,那么需要解复用rtmp + // 未获取到所有Track后,或者开启转协议,那么需要解复用rtmp _demuxer->inputRtmp(pkt); } RtmpMediaSource::onWrite(std::move(pkt)); } -int RtmpMediaSourceImp::totalReaderCount() -{ +int RtmpMediaSourceImp::totalReaderCount() { return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); } -void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) -{ - //不重复生成rtmp +void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) { + // 不重复生成rtmp _option = option; - //不重复生成rtmp协议 + // 不重复生成rtmp协议 _option.enable_rtmp = false; _muxer = std::make_shared(_tuple, _demuxer->getDuration(), _option); _muxer->setMediaListener(getListener()); _muxer->setTrackListener(std::static_pointer_cast(shared_from_this())); - //让_muxer对象拦截一部分事件(比如说录像相关事件) + // 让_muxer对象拦截一部分事件(比如说录像相关事件) MediaSource::setListener(_muxer); for (auto &track : _demuxer->getTracks(false)) { @@ -111,8 +129,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) } } -bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) -{ +bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) { if (_muxer) { if (_muxer->addTrack(track)) { track->addDelegate(_muxer); @@ -122,45 +139,38 @@ bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) return false; } -void RtmpMediaSourceImp::addTrackCompleted() -{ +void RtmpMediaSourceImp::addTrackCompleted() { if (_muxer) { _muxer->addTrackCompleted(); } } -void RtmpMediaSourceImp::resetTracks() -{ +void RtmpMediaSourceImp::resetTracks() { if (_muxer) { _muxer->resetTracks(); } } -void RtmpMediaSourceImp::onAllTrackReady() -{ +void RtmpMediaSourceImp::onAllTrackReady() { _all_track_ready = true; if (_recreate_metadata) { - //更新metadata + // 更新metadata for (auto &track : _muxer->getTracks()) { Metadata::addTrack(_metadata, track); } - RtmpMediaSource::updateMetaData(_metadata); + RtmpMediaSource::setMetaData(_metadata); } } -void RtmpMediaSourceImp::setListener(const std::weak_ptr &listener) -{ +void RtmpMediaSourceImp::setListener(const std::weak_ptr &listener) { if (_muxer) { //_muxer对象不能处理的事件再给listener处理 _muxer->setMediaListener(listener); - } - else { - //未创建_muxer对象,事件全部给listener处理 + } else { + // 未创建_muxer对象,事件全部给listener处理 MediaSource::setListener(listener); } } -} - - +} // namespace mediakit diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index e8c55f70..1f1cfdaa 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -183,10 +183,14 @@ void RtmpPusher::send_metaData(){ throw std::runtime_error("the media source was released"); } - AMFEncoder enc; - enc << "@setDataFrame" << "onMetaData" << src->getMetaData(); - sendRequest(MSG_DATA, enc.data()); + // metadata + src->getMetaData([&](const AMFValue &metadata) { + AMFEncoder enc; + enc << "@setDataFrame" << "onMetaData" << metadata; + sendRequest(MSG_DATA, enc.data()); + }); + // config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id); }); @@ -207,7 +211,16 @@ void RtmpPusher::send_metaData(){ if (++i == size) { strong_self->setSendFlushFlag(true); } - strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id); + if (rtmp->type_id == MSG_DATA) { + // update metadata + AMFEncoder enc; + enc << "@setDataFrame"; + auto pkt = enc.data(); + pkt.append(rtmp->data(), rtmp->size()); + strong_self->sendRequest(MSG_DATA, pkt); + } else { + strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id); + } }); }); _rtmp_reader->setDetachCB([weak_self]() { diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 56a799cb..ebdfb74e 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -291,17 +291,14 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr "description", "Now published." , "details", _media_info.stream, "clientid", "0"}); - - auto &metadata = src->getMetaData(); - if(metadata){ - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata - // onMetaData + // metadata + src->getMetaData([&](const AMFValue &metadata) { invoke.clear(); invoke << "onMetaData" << metadata; sendResponse(MSG_DATA, invoke.data()); - } + }); + // config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { onSendMedia(pkt); }); @@ -481,6 +478,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) { throw std::runtime_error("can only set metadata"); } _push_metadata = dec.load(); + _set_meta_data = false; } void RtmpSession::onProcessCmd(AMFDecoder &dec) { @@ -528,6 +526,7 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { } else if (type == "onMetaData") { //兼容某些不规范的推流器 _push_metadata = dec.load(); + _set_meta_data = false; } else { TraceP(this) << "unknown notify:" << type; } From 47add54465cf1b3625e236e5efb51c03ced80adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 22 Jul 2023 17:31:39 +0800 Subject: [PATCH 35/92] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81enhan?= =?UTF-8?q?ced-rtmp=20h265=20=E6=8E=A8=E6=B5=81=20(#2694)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Extension/Factory.cpp | 2 +- src/Extension/Factory.h | 10 ++ src/Extension/H264Rtmp.cpp | 4 - src/Extension/H265Rtmp.cpp | 204 ++++++++++++++++++++++++------------- src/Extension/H265Rtmp.h | 21 ++-- src/Rtmp/Rtmp.cpp | 49 +++++++++ src/Rtmp/Rtmp.h | 67 ++++++++++++ src/Rtmp/RtmpDemuxer.cpp | 62 ++++++----- src/Rtmp/RtmpDemuxer.h | 3 + src/Rtmp/RtmpPlayer.cpp | 7 ++ src/Rtmp/RtmpPusher.cpp | 7 ++ 11 files changed, 325 insertions(+), 111 deletions(-) diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index 4e0b795c..1bb292bd 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -211,7 +211,7 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){ return CodecInvalid; } -Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) { +Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) { switch (codecId){ case CodecH264 : return std::make_shared(); case CodecH265 : return std::make_shared(); diff --git a/src/Extension/Factory.h b/src/Extension/Factory.h index 6b0c3c0a..d0045865 100644 --- a/src/Extension/Factory.h +++ b/src/Extension/Factory.h @@ -21,6 +21,16 @@ namespace mediakit{ class Factory { public: + + /** + * 根据codec_id 获取track + * @param codecId 编码id + * @param sample_rate 采样率,视频固定为90000 + * @param channels 音频通道数 + * @param sample_bit 音频采样位数 + */ + static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0); + ////////////////////////////////rtsp相关////////////////////////////////// /** * 根据sdp生成Track对象 diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index f85ae425..2c338da8 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -164,12 +164,8 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { //not config _rtmp_packet->buffer[1] = true; int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } //cts set_be24(&_rtmp_packet->buffer[2], cts); - _rtmp_packet->time_stamp = dts; _rtmp_packet->body_size = _rtmp_packet->buffer.size(); _rtmp_packet->chunk_id = CHUNK_VIDEO; diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 493edac8..b3001ea0 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -12,12 +12,12 @@ #include "H265Rtmp.h" #ifdef ENABLE_MP4 #include "mpeg4-hevc.h" -#endif//ENABLE_MP4 +#endif // ENABLE_MP4 using namespace std; using namespace toolkit; -namespace mediakit{ +namespace mediakit { H265RtmpDecoder::H265RtmpDecoder() { _h265frame = obtainFrame(); @@ -30,11 +30,26 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() { } #ifdef ENABLE_MP4 + +static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) { + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) { + uint8_t *config = new uint8_t[bytes * 2]; + int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); + if (size > 4) { + frame.assign((char *)config + 4, size - 4); + } + delete[] config; + return size > 4; + } + return false; +} + /** * 返回不带0x00 00 00 01头的sps - * @return */ -static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) { +static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) { if (thiz.getMediaType() != FLV_CODEC_H265) { return false; } @@ -45,31 +60,78 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) { WarnL << "bad H265 cfg!"; return false; } - - auto extra = thiz.buffer.data() + 5; - auto bytes = thiz.buffer.size() - 5; - - struct mpeg4_hevc_t hevc; - memset(&hevc, 0, sizeof(hevc)); - if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) { - uint8_t *config = new uint8_t[bytes * 2]; - int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); - if (size > 4) { - frame.assign((char *) config + 4, size - 4); - } - delete [] config; - return size > 4; - } - return false; + return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame); } #endif void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { + if (_info.codec == CodecInvalid) { + // 先判断是否为增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + } + + if (_info.is_enhanced) { + // 增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + if (!_info.is_enhanced || _info.codec != CodecH265) { + throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!"); + } + auto data = (uint8_t *)pkt->data() + 5; + auto size = pkt->size() - 5; + switch (_info.video.pkt_type) { + case RtmpPacketType::PacketTypeSequenceStart: { +#ifdef ENABLE_MP4 + string config; + if (decode_HEVCDecoderConfigurationRecord(data, size, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); + } +#else + WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; +#endif + break; + } + + case RtmpPacketType::PacketTypeCodedFramesX: + case RtmpPacketType::PacketTypeCodedFrames: { + auto pts = pkt->time_stamp; + if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) { + // SI24 = [CompositionTime Offset] + CHECK(size > 7); + int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000; + pts += cts; + data += 3; + size -= 3; + } + splitFrame(data, size, pkt->time_stamp, pts); + break; + } + + case RtmpPacketType::PacketTypeMetadata: { + // The body does not contain video data. The body is an AMF encoded metadata. + // The metadata will be represented by a series of [name, value] pairs. + // For now the only defined [name, value] pair is [“colorInfo”, Object] + // See Metadata Frame section for more details of this object. + // + // For a deeper understanding of the encoding please see description + // of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec. + // DATA = [“colorInfo”, Object] + break; + } + case RtmpPacketType::PacketTypeSequenceEnd: { + // signals end of sequence + break; + } + default: break; + } + return; + } + + // 国内扩展(12) H265 rtmp if (pkt->isCfgFrame()) { #ifdef ENABLE_MP4 string config; - if(getH265ConfigFrame(*pkt,config)){ - onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp); + if (getH265ConfigFrame(*pkt, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); } #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; @@ -78,41 +140,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { } if (pkt->buffer.size() > 9) { - auto total_len = pkt->buffer.size(); - size_t offset = 5; - uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2); + uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; auto pts = pkt->time_stamp + cts; - while (offset + 4 < total_len) { - uint32_t frame_len; - memcpy(&frame_len, pkt->buffer.data() + offset, 4); - frame_len = ntohl(frame_len); - offset += 4; - if (frame_len + offset > total_len) { - break; - } - onGetH265(pkt->buffer.data() + offset, frame_len, pkt->time_stamp, pts); - offset += frame_len; - } + splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts); } } -inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) { - if(iLen == 0){ +void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) { + auto end = data + size; + while (data + 4 < end) { + uint32_t frame_len = load_be32(data); + data += 4; + if (data + frame_len > end) { + break; + } + onGetH265((const char *)data, frame_len, dts, pts); + data += frame_len; + } +} + +inline void H265RtmpDecoder::onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts) { + if (size == 0) { return; } #if 1 _h265frame->_dts = dts; _h265frame->_pts = pts; - _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头 - _h265frame->_buffer.append(pcData, iLen); + _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头 + _h265frame->_buffer.append(data, size); - //写入环形缓存 + // 写入环形缓存 RtmpCodec::inputFrame(_h265frame); _h265frame = obtainFrame(); #else - //防止内存拷贝,这样产生的265帧不会有0x00 00 01头 - auto frame = std::make_shared((char *)pcData,iLen,dts,pts,0); + // 防止内存拷贝,这样产生的265帧不会有0x00 00 01头 + auto frame = std::make_shared((char *)data, size, dts, pts, 0); RtmpCodec::inputFrame(frame); #endif } @@ -123,16 +186,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) { _track = dynamic_pointer_cast(track); } -void H265RtmpEncoder::makeConfigPacket(){ +void H265RtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { - //尝试从track中获取sps pps信息 + // 尝试从track中获取sps pps信息 _sps = _track->getSps(); _pps = _track->getPps(); _vps = _track->getVps(); } if (!_sps.empty() && !_pps.empty() && !_vps.empty()) { - //获取到sps/pps + // 获取到sps/pps makeVideoConfigPkt(); _got_config_frame = true; } @@ -175,20 +238,17 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (!_rtmp_packet) { _rtmp_packet = RtmpPacket::create(); - //flags/not_config/cts预占位 + // flags/not_config/cts预占位 _rtmp_packet->buffer.resize(5); } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags + // flags _rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config + // not config _rtmp_packet->buffer[1] = true; int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } - //cts + // cts set_be24(&_rtmp_packet->buffer[2], cts); _rtmp_packet->time_stamp = dts; @@ -196,10 +256,10 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { _rtmp_packet->chunk_id = CHUNK_VIDEO; _rtmp_packet->stream_index = STREAM_MEDIA; _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet + // 输出rtmp packet RtmpCodec::inputRtmp(_rtmp_packet); _rtmp_packet = nullptr; - }, &_rtmp_packet->buffer); + }, &_rtmp_packet->buffer); } void H265RtmpEncoder::makeVideoConfigPkt() { @@ -207,18 +267,16 @@ void H265RtmpEncoder::makeVideoConfigPkt() { int8_t flags = FLV_CODEC_H265; flags |= (FLV_KEY_FRAME << 4); bool is_config = true; - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back(!is_config); + // cts + pkt->buffer.append("\x0\x0\x0", 3); struct mpeg4_hevc_t hevc; memset(&hevc, 0, sizeof(hevc)); - string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + - string("\x00\x00\x00\x01", 4) + _sps + - string("\x00\x00\x00\x01", 4) + _pps; + string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps; h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL); uint8_t extra_data[1024]; int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data)); @@ -226,17 +284,17 @@ void H265RtmpEncoder::makeVideoConfigPkt() { WarnL << "生成H265 extra_data 失败"; return; } - //HEVCDecoderConfigurationRecord - rtmpPkt->buffer.append((char *)extra_data, extra_data_size); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + // HEVCDecoderConfigurationRecord + pkt->buffer.append((char *)extra_data, extra_data_size); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; #endif } -}//namespace mediakit +} // namespace mediakit diff --git a/src/Extension/H265Rtmp.h b/src/Extension/H265Rtmp.h index 593bb753..527728f6 100644 --- a/src/Extension/H265Rtmp.h +++ b/src/Extension/H265Rtmp.h @@ -15,7 +15,7 @@ #include "Extension/Track.h" #include "Extension/H265.h" -namespace mediakit{ +namespace mediakit { /** * h265 Rtmp解码类 * 将 h265 over rtmp 解复用出 h265-Frame @@ -25,7 +25,7 @@ public: using Ptr = std::shared_ptr; H265RtmpDecoder(); - ~H265RtmpDecoder() {} + ~H265RtmpDecoder() = default; /** * 输入265 Rtmp包 @@ -33,22 +33,23 @@ public: */ void inputRtmp(const RtmpPacket::Ptr &rtmp) override; - CodecId getCodecId() const override{ - return CodecH265; - } + CodecId getCodecId() const override { return CodecH265; } protected: - void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts); H265Frame::Ptr obtainFrame(); + void onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts); + void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts); + protected: + RtmpPacketInfo _info; H265Frame::Ptr _h265frame; }; /** * 265 Rtmp打包类 */ -class H265RtmpEncoder : public H265RtmpDecoder{ +class H265RtmpEncoder : public H265RtmpDecoder { public: using Ptr = std::shared_ptr; @@ -87,9 +88,9 @@ private: std::string _pps; H265Track::Ptr _track; RtmpPacket::Ptr _rtmp_packet; - FrameMerger _merger{FrameMerger::mp4_nal_size}; + FrameMerger _merger { FrameMerger::mp4_nal_size }; }; -}//namespace mediakit +} // namespace mediakit -#endif //ZLMEDIAKIT_H265RTMPCODEC_H +#endif // ZLMEDIAKIT_H265RTMPCODEC_H diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 6ef1698a..a5e34773 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -258,6 +258,55 @@ void RtmpHandshake::random_generate(char *bytes, int size) } } +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) { + RtmpPacketInfo save; + info = info ? info : &save; + info->codec = CodecInvalid; + + CHECK(size > 0); + if (data[0] >> 7 == 1) { + // IsExHeader == 1 + CHECK(size >= 5, "Invalid rtmp buffer size: ", size); + info->is_enhanced = true; + info->video.frame_type = (RtmpFrameType)((data[0] >> 4) & 0x07); + info->video.pkt_type = (RtmpPacketType)(data[0] & 0x0f); + if (memcmp(data + 1, "av01", 4) == 0) { + // AV1 + info->codec = CodecAV1; + } else if (memcmp(data + 1, "vp09", 4) == 0) { + // VP9 + info->codec = CodecVP9; + } else if (memcmp(data + 1, "hvc1", 4) == 0) { + // HEVC(H265) + info->codec = CodecH265; + } else { + WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); + } + } else { + // IsExHeader == 0 + info->is_enhanced = false; + info->video.frame_type = (RtmpFrameType)(data[0] >> 4); + info->video.rtmp_codec = (RtmpVideoCodec)(data[0] & 0x0f); + + switch (info->video.rtmp_codec) { + case RtmpVideoCodec::h264: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH264; + info->video.h264_pkt_type = (RtmpH264PacketType)data[1]; + break; + } + case RtmpVideoCodec::h265: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH265; + info->video.h264_pkt_type = (RtmpH264PacketType)data[1]; + break; + } + default: WarnL << "Rtmp video codec not supported: " << (int)info->video.rtmp_codec; break; + } + } + return info->codec; +} + }//namespace mediakit namespace toolkit { diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 739d4cf5..d33d9d72 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -269,5 +269,72 @@ private: //根据音频track获取flags uint8_t getAudioRtmpFlags(const Track::Ptr &track); +enum class RtmpFrameType : uint8_t { + reserved = 0, + key_frame = 1, // key frame (for AVC, a seekable frame) + inter_frame = 2, // inter frame (for AVC, a non-seekable frame) + disposable_inter_frame = 3, // disposable inter frame (H.263 only) + generated_key_frame = 4, // generated key frame (reserved for server use only) + video_info_frame = 5, // video info/command frame +}; + +enum class RtmpVideoCodec : uint8_t { + h263 = 2, // Sorenson H.263 + screen_video = 3, // Screen video + vp6 = 4, // On2 VP6 + vp6_alpha = 5, // On2 VP6 with alpha channel + screen_video2 = 6, // Screen video version 2 + h264 = 7, // avc + h265 = 12, // 国内扩展 +}; + +enum class RtmpH264PacketType : uint8_t { + h264_config_header = 0, // AVC sequence header(sps/pps) + h264_nalu = 1, // AVC NALU + h264_end_seq = 2, // AVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) +}; + +enum class RtmpPacketType : uint8_t { + PacketTypeSequenceStart = 0, + PacketTypeCodedFrames = 1, + PacketTypeSequenceEnd = 2, + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + PacketTypeCodedFramesX = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + PacketTypeMetadata = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + PacketTypeMPEG2TSSequenceStart = 5, +}; + +struct RtmpPacketInfo { + CodecId codec = CodecInvalid; + bool is_enhanced; + union { + struct { + RtmpFrameType frame_type; + RtmpVideoCodec rtmp_codec; + RtmpPacketType pkt_type; + RtmpH264PacketType h264_pkt_type; + } video; + }; +}; +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp-v1.pdf +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); + }//namespace mediakit #endif//__rtmp_h diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index b91dd503..625cb652 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -19,12 +19,12 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { size_t ret = 0; metadata.object_for_each([&](const string &key, const AMFValue &val) { if (key == "videocodecid") { - //找到视频 + // 找到视频 ++ret; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 ++ret; return; } @@ -32,7 +32,7 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { return ret; } -bool RtmpDemuxer::loadMetaData(const AMFValue &val){ +bool RtmpDemuxer::loadMetaData(const AMFValue &val) { bool ret = false; try { int audiosamplerate = 0; @@ -60,12 +60,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ return; } if (key == "videocodecid") { - //找到视频 + // 找到视频 videocodecid = &val; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 audiocodecid = &val; return; } @@ -75,16 +75,22 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ } if (key == "videodatarate") { videodatarate = val.as_integer(); + _videodatarate = videodatarate * 1024; return; } }); if (videocodecid) { - //有视频 + // 有视频 ret = true; - makeVideoTrack(*videocodecid, videodatarate * 1024); + if (videocodecid->type() == AMF_NUMBER && videocodecid->as_integer() == (int)RtmpVideoCodec::h264) { + // https://github.com/veovera/enhanced-rtmp/issues/8 + _complete_delay = true; + } else { + makeVideoTrack(*videocodecid, videodatarate * 1024); + } } if (audiocodecid) { - //有音频 + // 有音频 ret = true; makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024); } @@ -92,8 +98,8 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ WarnL << ex.what(); } - if (ret) { - //metadata中存在track相关的描述,那么我们根据metadata判断有多少个track + if (ret && !_complete_delay) { + // metadata中存在track相关的描述,那么我们根据metadata判断有多少个track addTrackCompleted(); } return ret; @@ -108,8 +114,14 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_VIDEO: { if (!_try_get_video_track) { _try_get_video_track = true; - auto codec = AMFValue(pkt->getMediaType()); - makeVideoTrack(codec, 0); + RtmpPacketInfo info; + auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &info); + if (codec_id != CodecInvalid) { + makeVideoTrack(Factory::getTrackByCodecId(codec_id), _videodatarate); + if (_complete_delay) { + addTrackCompleted(); + } + } } if (_video_rtmp_decoder) { _video_rtmp_decoder->inputRtmp(pkt); @@ -128,51 +140,55 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { } break; } - default : break; + default: break; } } void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) { + makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate); +} + +void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) { if (_video_rtmp_decoder) { return; } - //生成Track对象 - _video_track = dynamic_pointer_cast(Factory::getVideoTrackByAmf(videoCodec)); + // 生成Track对象 + _video_track = dynamic_pointer_cast(track); if (!_video_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _video_rtmp_decoder = Factory::getRtmpCodecByTrack(_video_track, false); if (!_video_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _video_track.reset(); return; } _video_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _video_rtmp_decoder->addDelegate(_video_track); addTrack(_video_track); _try_get_video_track = true; } -void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int channels, int sample_bit, int bit_rate) { +void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) { if (_audio_rtmp_decoder) { return; } - //生成Track对象 + // 生成Track对象 _audio_track = dynamic_pointer_cast(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit)); if (!_audio_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audio_track, false); if (!_audio_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _audio_track.reset(); return; } _audio_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _audio_rtmp_decoder->addDelegate(_audio_track); addTrack(_audio_track); _try_get_audio_track = true; diff --git a/src/Rtmp/RtmpDemuxer.h b/src/Rtmp/RtmpDemuxer.h index 31b0ed2d..df810652 100644 --- a/src/Rtmp/RtmpDemuxer.h +++ b/src/Rtmp/RtmpDemuxer.h @@ -45,12 +45,15 @@ public: private: void makeVideoTrack(const AMFValue &val, int bit_rate); + void makeVideoTrack(const Track::Ptr &val, int bit_rate); void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate); private: bool _try_get_video_track = false; bool _try_get_audio_track = false; + bool _complete_delay = false; float _duration = 0; + int _videodatarate = 0; AudioTrack::Ptr _audio_track; VideoTrack::Ptr _video_track; RtmpCodec::Ptr _audio_rtmp_decoder; diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index 9546d889..7bc0acdf 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -191,6 +191,13 @@ void RtmpPlayer::send_connect() { obj.set("audioCodecs", (double) (0x0400)); //只支持H264 obj.set("videoCodecs", (double) (0x0080)); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + sendInvoke("connect", obj); addOnResultCB([this](AMFDecoder &dec) { //TraceL << "connect result"; diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index 1f1cfdaa..f401cf34 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -135,6 +135,13 @@ void RtmpPusher::send_connect() { obj.set("type", "nonprivate"); obj.set("tcUrl", _tc_url); obj.set("swfUrl", _tc_url); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + sendInvoke("connect", obj); addOnResultCB([this](AMFDecoder &dec) { //TraceL << "connect result"; From a86398b6db20f3b4bd75001f95881fd815b3ec21 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 22 Jul 2023 18:54:59 +0800 Subject: [PATCH 36/92] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8E=E5=AE=8C?= =?UTF-8?q?=E5=96=84rtmp=E5=8D=8F=E8=AE=AE=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rtmp相关常量由宏改为枚举 明确rtmp包一些字段赋值含义 --- src/Extension/AACRtmp.cpp | 62 +++++++------- src/Extension/Factory.cpp | 34 ++++---- src/Extension/H264Rtmp.cpp | 92 ++++++++++----------- src/Extension/H265Rtmp.cpp | 41 +++++----- src/Rtmp/Rtmp.cpp | 163 +++++++++++++++++-------------------- src/Rtmp/Rtmp.h | 71 +++++++++++----- src/Rtmp/RtmpDemuxer.cpp | 2 +- 7 files changed, 232 insertions(+), 233 deletions(-) diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index 1454a727..d18d9d25 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -18,7 +18,7 @@ namespace mediakit { static string getAacCfg(const RtmpPacket &thiz) { string ret; - if (thiz.getMediaType() != FLV_CODEC_AAC) { + if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) { return ret; } if (!thiz.isCfgFrame()) { @@ -93,51 +93,45 @@ void AACRtmpEncoder::makeConfigPacket() { bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (_aac_cfg.empty()) { if (frame->prefixSize()) { - //包含adts头,从adts头获取aac配置信息 - _aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize()); + // 包含adts头,从adts头获取aac配置信息 + _aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize()); } makeConfigPacket(); } - if(_aac_cfg.empty()){ + if (_aac_cfg.empty()) { return false; } - auto rtmpPkt = RtmpPacket::create(); - //header - uint8_t is_config = false; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - - //aac data - rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = frame->dts(); - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw); + // aac data + pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = frame->dts(); + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); return true; } void AACRtmpEncoder::makeAudioConfigPkt() { _audio_flv_flags = getAudioRtmpFlags(std::make_shared(_aac_cfg)); - auto rtmpPkt = RtmpPacket::create(); - - //header - uint8_t is_config = true; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - //aac config - rtmpPkt->buffer.append(_aac_cfg); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header); + // aac config + pkt->buffer.append(_aac_cfg); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index 1bb292bd..a19d0f66 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -201,11 +201,11 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){ } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpVideoCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_H264 : return CodecH264; - case FLV_CODEC_H265 : return CodecH265; - default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid; + case RtmpVideoCodec::h264: return CodecH264; + case RtmpVideoCodec::h265: return CodecH265; + default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid; } } return CodecInvalid; @@ -243,13 +243,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) { } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpAudioCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_AAC : return CodecAAC; - case FLV_CODEC_G711A : return CodecG711A; - case FLV_CODEC_G711U : return CodecG711U; - case FLV_CODEC_OPUS : return CodecOpus; - default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid; + case RtmpAudioCodec::aac : return CodecAAC; + case RtmpAudioCodec::g711a : return CodecG711A; + case RtmpAudioCodec::g711u : return CodecG711U; + case RtmpAudioCodec::opus : return CodecOpus; + default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid; } } @@ -291,13 +291,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc } AMFValue Factory::getAmfByCodecId(CodecId codecId) { - switch (codecId){ - case CodecAAC: return AMFValue(FLV_CODEC_AAC); - case CodecH264: return AMFValue(FLV_CODEC_H264); - case CodecH265: return AMFValue(FLV_CODEC_H265); - case CodecG711A: return AMFValue(FLV_CODEC_G711A); - case CodecG711U: return AMFValue(FLV_CODEC_G711U); - case CodecOpus: return AMFValue(FLV_CODEC_OPUS); + switch (codecId) { + case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac); + case CodecH264: return AMFValue((int)RtmpVideoCodec::h264); + case CodecH265: return AMFValue((int)RtmpVideoCodec::h265); + case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a); + case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u); + case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus); default: return AMFValue(AMF_NULL); } } diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index 2c338da8..f93c5a00 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -30,7 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() { * 返回不带0x00 00 00 01头的sps pps */ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { - if (thiz.getMediaType() != FLV_CODEC_H264) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) { return false; } if (!thiz.isCfgFrame()) { @@ -159,22 +159,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags - _rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - //cts - set_be24(&_rtmp_packet->buffer[2], cts); - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; - }, &_rtmp_packet->buffer); + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); } void H264RtmpEncoder::makeVideoConfigPkt() { @@ -182,42 +181,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() { WarnL << "sps长度不足4字节"; return; } - int8_t flags = FLV_CODEC_H264; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; - - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); - - //AVCDecoderConfigurationRecord start - rtmpPkt->buffer.push_back(1); // version - rtmpPkt->buffer.push_back(_sps[1]); // profile - rtmpPkt->buffer.push_back(_sps[2]); // compat - rtmpPkt->buffer.push_back(_sps[3]); // level - rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) - rtmpPkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) - //sps + auto flags = (uint8_t)RtmpVideoCodec::h264; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); + // AVCDecoderConfigurationRecord start + pkt->buffer.push_back(1); // version + pkt->buffer.push_back(_sps[1]); // profile + pkt->buffer.push_back(_sps[2]); // compat + pkt->buffer.push_back(_sps[3]); // level + pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) + pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) + // sps uint16_t size = (uint16_t)_sps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_sps); - //pps - rtmpPkt->buffer.push_back(1); // version + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_sps); + // pps + pkt->buffer.push_back(1); // version size = (uint16_t)_pps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_pps); + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_pps); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index b3001ea0..451310b1 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -50,7 +50,7 @@ static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, * 返回不带0x00 00 00 01头的sps */ static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) { - if (thiz.getMediaType() != FLV_CODEC_H265) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) { return false; } if (!thiz.isCfgFrame()) { @@ -243,34 +243,31 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - // flags - _rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - // not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - // cts - set_be24(&_rtmp_packet->buffer[2], cts); - - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - // 输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; - }, &_rtmp_packet->buffer); + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); } void H265RtmpEncoder::makeVideoConfigPkt() { #ifdef ENABLE_MP4 - int8_t flags = FLV_CODEC_H265; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; + auto flags = (uint8_t)RtmpVideoCodec::h265; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); auto pkt = RtmpPacket::create(); // header pkt->buffer.push_back(flags); - pkt->buffer.push_back(!is_config); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); // cts pkt->buffer.append("\x0\x0\x0", 3); diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index a5e34773..84eb5dbc 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -10,10 +10,10 @@ #include "Rtmp.h" #include "Extension/Factory.h" -namespace mediakit{ -TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) -{ +namespace mediakit { + +TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) { _metadata.set("duration", dur_sec); _metadata.set("fileSize", (int)fileSize); _metadata.set("title", std::string("Streamed by ") + kServerName); @@ -22,14 +22,14 @@ TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::mapgetVideoWidth() > 0 ){ +VideoMeta::VideoMeta(const VideoTrack::Ptr &video) { + if (video->getVideoWidth() > 0) { _metadata.set("width", video->getVideoWidth()); } - if(video->getVideoHeight() > 0 ){ + if (video->getVideoHeight() > 0) { _metadata.set("height", video->getVideoHeight()); } - if(video->getVideoFps() > 0 ){ + if (video->getVideoFps() > 0) { _metadata.set("framerate", video->getVideoFps()); } if (video->getBitRate()) { @@ -39,26 +39,26 @@ VideoMeta::VideoMeta(const VideoTrack::Ptr &video){ _metadata.set("videocodecid", Factory::getAmfByCodecId(_codecId)); } -AudioMeta::AudioMeta(const AudioTrack::Ptr &audio){ +AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) { if (audio->getBitRate()) { _metadata.set("audiodatarate", audio->getBitRate() / 1024); } - if(audio->getAudioSampleRate() > 0){ + if (audio->getAudioSampleRate() > 0) { _metadata.set("audiosamplerate", audio->getAudioSampleRate()); } - if(audio->getAudioSampleBit() > 0){ + if (audio->getAudioSampleBit() > 0) { _metadata.set("audiosamplesize", audio->getAudioSampleBit()); } - if(audio->getAudioChannel() > 0){ + if (audio->getAudioChannel() > 0) { _metadata.set("stereo", audio->getAudioChannel() > 1); } _codecId = audio->getCodecId(); _metadata.set("audiocodecid", Factory::getAmfByCodecId(_codecId)); } -uint8_t getAudioRtmpFlags(const Track::Ptr &track){ - switch (track->getTrackType()){ - case TrackAudio : { +uint8_t getAudioRtmpFlags(const Track::Ptr &track) { + switch (track->getTrackType()) { + case TrackAudio: { auto audioTrack = std::dynamic_pointer_cast(track); if (!audioTrack) { WarnL << "获取AudioTrack失败"; @@ -68,21 +68,21 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ auto iChannel = audioTrack->getAudioChannel(); auto iSampleBit = audioTrack->getAudioSampleBit(); - uint8_t flvAudioType ; - switch (track->getCodecId()){ - case CodecG711A : flvAudioType = FLV_CODEC_G711A; break; - case CodecG711U : flvAudioType = FLV_CODEC_G711U; break; - case CodecOpus : { - flvAudioType = FLV_CODEC_OPUS; - //opus不通过flags获取音频相关信息 + uint8_t flvAudioType; + switch (track->getCodecId()) { + case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break; + case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break; + case CodecOpus: { + flvAudioType = (uint8_t)RtmpAudioCodec::opus; + // opus不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; break; } - case CodecAAC : { - flvAudioType = FLV_CODEC_AAC; - //aac不通过flags获取音频相关信息 + case CodecAAC: { + flvAudioType = (uint8_t)RtmpAudioCodec::aac; + // aac不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; @@ -93,23 +93,15 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ uint8_t flvSampleRate; switch (iSampleRate) { - case 44100: - flvSampleRate = 3; - break; - case 22050: - flvSampleRate = 2; - break; - case 11025: - flvSampleRate = 1; - break; + case 44100: flvSampleRate = 3; break; + case 22050: flvSampleRate = 2; break; + case 11025: flvSampleRate = 1; break; case 16000: // nellymoser only case 8000: // nellymoser only case 5512: // not MP3 flvSampleRate = 0; break; - default: - WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; - return 0; + default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0; } uint8_t flvStereoOrMono = (iChannel > 1); @@ -117,32 +109,28 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono; } - default : return 0; + default: return 0; } } - void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) { Metadata::Ptr new_metadata; switch (track->getTrackType()) { case TrackVideo: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; + } case TrackAudio: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; - default: - return; + } + default: return; } - new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { - metadata.set(key, value); - }); + new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); }); } -RtmpPacket::Ptr RtmpPacket::create(){ +RtmpPacket::Ptr RtmpPacket::create() { #if 0 static ResourcePool packet_pool; static onceToken token([]() { @@ -156,8 +144,7 @@ RtmpPacket::Ptr RtmpPacket::create(){ #endif } -void RtmpPacket::clear() -{ +void RtmpPacket::clear() { is_abs_stamp = false; time_stamp = 0; ts_field = 0; @@ -165,36 +152,42 @@ void RtmpPacket::clear() buffer.clear(); } -bool RtmpPacket::isVideoKeyFrame() const -{ - return type_id == MSG_VIDEO && (uint8_t)buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t)buffer[1] == 1; +bool RtmpPacket::isVideoKeyFrame() const { + if (type_id != MSG_VIDEO) { + return false; + } + RtmpPacketInfo info; + if (CodecInvalid == parseVideoRtmpPacket((uint8_t *)data(), size(), &info)) { + return false; + } + if (info.is_enhanced) { + return info.video.frame_type == RtmpFrameType::key_frame && info.video.pkt_type == RtmpPacketType::PacketTypeCodedFramesX; + } + return info.video.frame_type == RtmpFrameType::key_frame && info.video.h264_pkt_type == RtmpH264PacketType::h264_nalu; } -bool RtmpPacket::isCfgFrame() const -{ +bool RtmpPacket::isCfgFrame() const { switch (type_id) { - case MSG_VIDEO: return buffer[1] == 0; - case MSG_AUDIO: { - switch (getMediaType()) { - case FLV_CODEC_AAC: return buffer[1] == 0; - default: return false; + case MSG_VIDEO: return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; + case MSG_AUDIO: { + switch ((RtmpAudioCodec)getRtmpCodecId()) { + case RtmpAudioCodec::aac: return (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; + default: return false; + } } - } - default: return false; + default: return false; } } -int RtmpPacket::getMediaType() const -{ +int RtmpPacket::getRtmpCodecId() const { switch (type_id) { - case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; - case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; - default: return 0; + case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; + case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; + default: return 0; } } -int RtmpPacket::getAudioSampleRate() const -{ +int RtmpPacket::getAudioSampleRate() const { if (type_id != MSG_AUDIO) { return 0; } @@ -203,8 +196,7 @@ int RtmpPacket::getAudioSampleRate() const return sampleRate[flvSampleRate]; } -int RtmpPacket::getAudioSampleBit() const -{ +int RtmpPacket::getAudioSampleBit() const { if (type_id != MSG_AUDIO) { return 0; } @@ -213,8 +205,7 @@ int RtmpPacket::getAudioSampleBit() const return sampleBit[flvSampleBit]; } -int RtmpPacket::getAudioChannel() const -{ +int RtmpPacket::getAudioChannel() const { if (type_id != MSG_AUDIO) { return 0; } @@ -223,8 +214,7 @@ int RtmpPacket::getAudioChannel() const return channel[flvStereoOrMono]; } -RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) -{ +RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) { is_abs_stamp = that.is_abs_stamp; stream_index = that.stream_index; body_size = that.body_size; @@ -234,25 +224,20 @@ RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) return *this; } -RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) -{ +RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) { _time = htonl(_time); memcpy(time_stamp, &_time, 4); if (!_random) { random_generate((char *)random, sizeof(random)); - } - else { + } else { memcpy(random, _random, sizeof(random)); } } -void RtmpHandshake::random_generate(char *bytes, int size) -{ - static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, - 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d }; +void RtmpHandshake::random_generate(char *bytes, int size) { + static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6d }; for (int i = 0; i < size; i++) { bytes[i] = cdata[rand() % (sizeof(cdata) - 1)]; } @@ -286,9 +271,9 @@ CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *i // IsExHeader == 0 info->is_enhanced = false; info->video.frame_type = (RtmpFrameType)(data[0] >> 4); - info->video.rtmp_codec = (RtmpVideoCodec)(data[0] & 0x0f); + auto rtmp_codec = (RtmpVideoCodec)(data[0] & 0x0f); - switch (info->video.rtmp_codec) { + switch (rtmp_codec) { case RtmpVideoCodec::h264: { CHECK(size >= 1, "Invalid rtmp buffer size: ", size); info->codec = CodecH264; @@ -301,14 +286,14 @@ CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *i info->video.h264_pkt_type = (RtmpH264PacketType)data[1]; break; } - default: WarnL << "Rtmp video codec not supported: " << (int)info->video.rtmp_codec; break; + default: WarnL << "Rtmp video codec not supported: " << (int)rtmp_codec; break; } } return info->codec; } -}//namespace mediakit +} // namespace mediakit namespace toolkit { - StatisticImp(mediakit::RtmpPacket); +StatisticImp(mediakit::RtmpPacket); } \ No newline at end of file diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index d33d9d72..01fc2fbf 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -63,18 +63,6 @@ #define CHUNK_AUDIO 6 /*音频chunkID*/ #define CHUNK_VIDEO 7 /*视频chunkID*/ -#define FLV_KEY_FRAME 1 -#define FLV_INTER_FRAME 2 - -#define FLV_CODEC_AAC 10 -#define FLV_CODEC_H264 7 -//金山扩展: https://github.com/ksvc/FFmpeg/wiki -#define FLV_CODEC_H265 12 -#define FLV_CODEC_G711A 7 -#define FLV_CODEC_G711U 8 -//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10 -#define FLV_CODEC_OPUS 13 - namespace mediakit { #if defined(_WIN32) @@ -181,12 +169,9 @@ public: } void clear(); - bool isVideoKeyFrame() const; bool isCfgFrame() const; - - int getMediaType() const; - + int getRtmpCodecId() const; int getAudioSampleRate() const; int getAudioSampleBit() const; int getAudioChannel() const; @@ -269,6 +254,10 @@ private: //根据音频track获取flags uint8_t getAudioRtmpFlags(const Track::Ptr &track); +////////////////// rtmp video ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Type of video frame. enum class RtmpFrameType : uint8_t { reserved = 0, key_frame = 1, // key frame (for AVC, a seekable frame) @@ -278,6 +267,7 @@ enum class RtmpFrameType : uint8_t { video_info_frame = 5, // video info/command frame }; +// UB [4]; Codec Identifier. enum class RtmpVideoCodec : uint8_t { h263 = 2, // Sorenson H.263 screen_video = 3, // Screen video @@ -288,12 +278,15 @@ enum class RtmpVideoCodec : uint8_t { h265 = 12, // 国内扩展 }; +// UI8; enum class RtmpH264PacketType : uint8_t { - h264_config_header = 0, // AVC sequence header(sps/pps) - h264_nalu = 1, // AVC NALU - h264_end_seq = 2, // AVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) + h264_config_header = 0, // AVC or HEVC sequence header(sps/pps) + h264_nalu = 1, // AVC or HEVC NALU + h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) }; +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf +// UB[4] enum class RtmpPacketType : uint8_t { PacketTypeSequenceStart = 0, PacketTypeCodedFrames = 1, @@ -321,15 +314,49 @@ enum class RtmpPacketType : uint8_t { PacketTypeMPEG2TSSequenceStart = 5, }; +////////////////// rtmp audio ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Format of SoundData +enum class RtmpAudioCodec : uint8_t { + /** + 0 = Linear PCM, platform endian + 1 = ADPCM + 2 = MP3 + 3 = Linear PCM, little endian + 4 = Nellymoser 16 kHz mono + 5 = Nellymoser 8 kHz mono + 6 = Nellymoser + 7 = G.711 A-law logarithmic PCM + 8 = G.711 mu-law logarithmic PCM + 9 = reserved + 10 = AAC + 11 = Speex + 14 = MP3 8 kHz + 15 = Device-specific sound + */ + g711a = 7, + g711u = 8, + aac = 10, + opus = 13 // 国内扩展 +}; + +// UI8; +enum class RtmpAACPacketType : uint8_t { + aac_config_header = 0, // AAC sequence header + aac_raw = 1, // AAC raw +}; + +//////////////////////////////////////////// + struct RtmpPacketInfo { CodecId codec = CodecInvalid; bool is_enhanced; union { struct { RtmpFrameType frame_type; - RtmpVideoCodec rtmp_codec; - RtmpPacketType pkt_type; - RtmpH264PacketType h264_pkt_type; + RtmpPacketType pkt_type; // enhanced = true + RtmpH264PacketType h264_pkt_type; // enhanced = false } video; }; }; diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index 625cb652..e591acbf 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -132,7 +132,7 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_AUDIO: { if (!_try_get_audio_track) { _try_get_audio_track = true; - auto codec = AMFValue(pkt->getMediaType()); + auto codec = AMFValue(pkt->getRtmpCodecId()); makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0); } if (_audio_rtmp_decoder) { From 780a1eb9fc5d55529fa1f67fd8606cd484c01b00 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 22 Jul 2023 19:32:01 +0800 Subject: [PATCH 37/92] =?UTF-8?q?=E5=AE=8C=E5=96=84rtmp=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=B8=A7=E4=B8=8E=E9=85=8D=E7=BD=AE=E5=B8=A7=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Extension/AACRtmp.cpp | 3 --- src/Extension/H264Rtmp.cpp | 3 --- src/Extension/H265Rtmp.cpp | 3 --- src/Rtmp/Rtmp.cpp | 34 ++++++++++++++++++++++++---------- src/Rtmp/Rtmp.h | 2 ++ 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index d18d9d25..9fbd8fd1 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -21,9 +21,6 @@ static string getAacCfg(const RtmpPacket &thiz) { if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) { return ret; } - if (!thiz.isCfgFrame()) { - return ret; - } if (thiz.buffer.size() < 4) { WarnL << "get aac config failed, rtmp packet is: " << hexdump(thiz.data(), thiz.size()); return ret; diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index f93c5a00..eb6e5c4d 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -33,9 +33,6 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) { return false; } - if (!thiz.isCfgFrame()) { - return false; - } if (thiz.buffer.size() < 13) { return false; } diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 451310b1..6c2af5fd 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -53,9 +53,6 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) { if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) { return false; } - if (!thiz.isCfgFrame()) { - return false; - } if (thiz.buffer.size() < 6) { WarnL << "bad H265 cfg!"; return false; diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 84eb5dbc..92c5f905 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -156,22 +156,36 @@ bool RtmpPacket::isVideoKeyFrame() const { if (type_id != MSG_VIDEO) { return false; } - RtmpPacketInfo info; - if (CodecInvalid == parseVideoRtmpPacket((uint8_t *)data(), size(), &info)) { - return false; + RtmpFrameType frame_type; + if (buffer[0] >> 7 == 1) { + // IsExHeader == 1 + frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07); + } else { + // IsExHeader == 0 + frame_type = (RtmpFrameType)(buffer[0] >> 4); } - if (info.is_enhanced) { - return info.video.frame_type == RtmpFrameType::key_frame && info.video.pkt_type == RtmpPacketType::PacketTypeCodedFramesX; - } - return info.video.frame_type == RtmpFrameType::key_frame && info.video.h264_pkt_type == RtmpH264PacketType::h264_nalu; + return frame_type == RtmpFrameType::key_frame; } bool RtmpPacket::isCfgFrame() const { switch (type_id) { - case MSG_VIDEO: return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; case MSG_AUDIO: { - switch ((RtmpAudioCodec)getRtmpCodecId()) { - case RtmpAudioCodec::aac: return (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; + return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; + } + case MSG_VIDEO: { + if (!isVideoKeyFrame()) { + return false; + } + if (buffer[0] >> 7 == 1) { + // IsExHeader == 1 + return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart; + } + // IsExHeader == 0 + switch ((RtmpVideoCodec)getRtmpCodecId()) { + case RtmpVideoCodec::h265: + case RtmpVideoCodec::h264: { + return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; + } default: return false; } } diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 01fc2fbf..9d83ffa9 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -169,7 +169,9 @@ public: } void clear(); + // video config frame和key frame都返回true bool isVideoKeyFrame() const; + // aac config或h264/h265 config bool isCfgFrame() const; int getRtmpCodecId() const; int getAudioSampleRate() const; From 3e9a8b9d8b5683eb085ee39eb486e7c5a495db37 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 22 Jul 2023 19:40:28 +0800 Subject: [PATCH 38/92] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isCfgFrame -> isConfigFrame getAacCfg -> getConfig --- src/Extension/AAC.cpp | 4 ++-- src/Extension/AAC.h | 2 +- src/Extension/AACRtmp.cpp | 8 ++++---- src/Extension/AACRtp.cpp | 2 +- src/Extension/H264Rtmp.cpp | 2 +- src/Extension/H265Rtmp.cpp | 2 +- src/Record/MP4Demuxer.cpp | 2 +- src/Record/MP4Muxer.cpp | 4 ++-- src/Rtmp/FlvPlayer.cpp | 2 +- src/Rtmp/Rtmp.cpp | 2 +- src/Rtmp/Rtmp.h | 9 +++++++-- src/Rtmp/RtmpMediaSourceImp.cpp | 2 +- src/Rtmp/RtmpPlayer.cpp | 2 +- 13 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Extension/AAC.cpp b/src/Extension/AAC.cpp index ea57d643..1047a8b3 100644 --- a/src/Extension/AAC.cpp +++ b/src/Extension/AAC.cpp @@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) { onReady(); } -const string &AACTrack::getAacCfg() const { +const string &AACTrack::getConfig() const { return _cfg; } @@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() { WarnL << getCodecName() << " Track未准备好"; return nullptr; } - return std::make_shared(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); + return std::make_shared(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index ad7479f6..b95fc6f2 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -44,7 +44,7 @@ public: /** * 获取aac 配置信息 */ - const std::string &getAacCfg() const; + const std::string &getConfig() const; bool ready() override; CodecId getCodecId() const override; diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index 9fbd8fd1..f1fdbab9 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -16,7 +16,7 @@ using namespace toolkit; namespace mediakit { -static string getAacCfg(const RtmpPacket &thiz) { +static string getConfig(const RtmpPacket &thiz) { string ret; if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) { return ret; @@ -30,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) { } void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { - _aac_cfg = getAacCfg(*pkt); + if (pkt->isConfigFrame()) { + _aac_cfg = getConfig(*pkt); if (!_aac_cfg.empty()) { onGetAAC(nullptr, 0, 0); } @@ -79,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) { void AACRtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { //从track中和获取aac配置信息 - _aac_cfg = _track->getAacCfg(); + _aac_cfg = _track->getConfig(); } if (!_aac_cfg.empty()) { diff --git a/src/Extension/AACRtp.cpp b/src/Extension/AACRtp.cpp index 97c84ff6..b5c5ab39 100644 --- a/src/Extension/AACRtp.cpp +++ b/src/Extension/AACRtp.cpp @@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) { if (!aacTrack || !aacTrack->ready()) { WarnL << "该aac track无效!"; } else { - _aac_cfg = aacTrack->getAacCfg(); + _aac_cfg = aacTrack->getConfig(); } obtainFrame(); } diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index eb6e5c4d..9e62896d 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -56,7 +56,7 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { } void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { + if (pkt->isConfigFrame()) { //缓存sps pps,后续插入到I帧之前 if (!getH264Config(*pkt, _sps, _pps)) { WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size()); diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 6c2af5fd..af685d36 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -124,7 +124,7 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { } // 国内扩展(12) H265 rtmp - if (pkt->isCfgFrame()) { + if (pkt->isConfigFrame()) { #ifdef ENABLE_MP4 string config; if (getH265ConfigFrame(*pkt, config)) { diff --git a/src/Record/MP4Demuxer.cpp b/src/Record/MP4Demuxer.cpp index a3f29958..02c66976 100644 --- a/src/Record/MP4Demuxer.cpp +++ b/src/Record/MP4Demuxer.cpp @@ -247,7 +247,7 @@ Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int6 AACTrack::Ptr track = dynamic_pointer_cast(it->second); assert(track); //加上adts头 - dumpAacConfig(track->getAacCfg(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN); + dumpAacConfig(track->getConfig(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN); ret = std::make_shared >(buf, (uint64_t)dts, (uint64_t)pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec); break; } diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index 69db4237..c79cc132 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -234,8 +234,8 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) { audio_track->getAudioChannel(), audio_track->getAudioSampleBit() * audio_track->getAudioChannel(), audio_track->getAudioSampleRate(), - audio_track->getAacCfg().data(), - audio_track->getAacCfg().size()); + audio_track->getConfig().data(), + audio_track->getConfig().size()); if (track_id < 0) { WarnL << "添加AAC Track失败:" << track_id; return false; diff --git a/src/Rtmp/FlvPlayer.cpp b/src/Rtmp/FlvPlayer.cpp index c0dc1dfa..fbefd23e 100644 --- a/src/Rtmp/FlvPlayer.cpp +++ b/src/Rtmp/FlvPlayer.cpp @@ -62,7 +62,7 @@ bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { } void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { - if (!_play_result && !packet->isCfgFrame()) { + if (!_play_result && !packet->isConfigFrame()) { _play_result = true; onPlayResult(SockException(Err_success, "play http-flv success")); } diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 92c5f905..4fa8bce3 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -167,7 +167,7 @@ bool RtmpPacket::isVideoKeyFrame() const { return frame_type == RtmpFrameType::key_frame; } -bool RtmpPacket::isCfgFrame() const { +bool RtmpPacket::isConfigFrame() const { switch (type_id) { case MSG_AUDIO: { return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 9d83ffa9..3ebe815f 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -169,10 +169,15 @@ public: } void clear(); + // video config frame和key frame都返回true + // 用于gop缓存定位 bool isVideoKeyFrame() const; - // aac config或h264/h265 config - bool isCfgFrame() const; + + // aac config或h264/h265 config返回true,支持增强型rtmp + // 用于缓存解码配置信息 + bool isConfigFrame() const; + int getRtmpCodecId() const; int getAudioSampleRate() const; int getAudioSampleBit() const; diff --git a/src/Rtmp/RtmpMediaSourceImp.cpp b/src/Rtmp/RtmpMediaSourceImp.cpp index ee072b8e..4a6f8975 100644 --- a/src/Rtmp/RtmpMediaSourceImp.cpp +++ b/src/Rtmp/RtmpMediaSourceImp.cpp @@ -54,7 +54,7 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { default: break; } - if (pkt->isCfgFrame()) { + if (pkt->isConfigFrame()) { std::lock_guard lock(_mtx); _config_frame_map[pkt->type_id] = pkt; if (!_ring) { diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index 7bc0acdf..42914cda 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -339,7 +339,7 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { return; } - if (chunk_data->isCfgFrame()) { + if (chunk_data->isConfigFrame()) { //输入配置帧以便初始化完成各个track onRtmpPacket(chunk_data); } else { From b580d6c736e496d7b375b440c73d0c8a696c1d8d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 22 Jul 2023 20:08:54 +0800 Subject: [PATCH 39/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E4=B8=BA=E5=A2=9E=E5=BC=BA=E5=9E=8Brtmp?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=9B=B8=E5=85=B3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 有符合整型右移7位可能为-1(而不是1) 这样将导致在处理增强型rtmp时,判断关键帧和配置帧失败 --- src/Rtmp/Rtmp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 4fa8bce3..8dd7505d 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -157,7 +157,7 @@ bool RtmpPacket::isVideoKeyFrame() const { return false; } RtmpFrameType frame_type; - if (buffer[0] >> 7 == 1) { + if (buffer[0] >> 7) { // IsExHeader == 1 frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07); } else { @@ -176,7 +176,7 @@ bool RtmpPacket::isConfigFrame() const { if (!isVideoKeyFrame()) { return false; } - if (buffer[0] >> 7 == 1) { + if (buffer[0] >> 7) { // IsExHeader == 1 return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart; } @@ -263,7 +263,7 @@ CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *i info->codec = CodecInvalid; CHECK(size > 0); - if (data[0] >> 7 == 1) { + if (data[0] >> 7) { // IsExHeader == 1 CHECK(size >= 5, "Invalid rtmp buffer size: ", size); info->is_enhanced = true; From 931766505891cf1c22193c28bbfbc8c1e6afd91d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 22 Jul 2023 23:11:40 +0800 Subject: [PATCH 40/92] =?UTF-8?q?=E6=94=AF=E6=8C=81enhanced-rtmp(H265)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + README_en.md | 1 + src/Rtmp/Rtmp.h | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b3ea16cf..c8ce0a2b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ - 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议 - 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器 diff --git a/README_en.md b/README_en.md index 84894a74..1d83966d 100644 --- a/README_en.md +++ b/README_en.md @@ -68,6 +68,7 @@ - Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol - Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 3ebe815f..c7fd5262 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -367,7 +367,7 @@ struct RtmpPacketInfo { } video; }; }; -// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp-v1.pdf +// https://github.com/veovera/enhanced-rtmp CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); }//namespace mediakit From e8f8b48d3828e1eba3de5ef7983568f379723903 Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Tue, 25 Jul 2023 15:47:28 +0800 Subject: [PATCH 41/92] fix rtsp player pull hik record stream (#2624 #2701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复rtsp 拉流 sdp中全局a:control:获取失败的bug --- src/Rtsp/Rtsp.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index b2d2b86b..f4963531 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -166,13 +166,13 @@ void SdpParser::load(const string &sdp) { string opt_val = line.substr(2); switch (opt) { case 't': - if (_track_vec.empty()) + if (_track_vec.size() == 1) global_infos.emplace(opt, opt_val); else track->_t = opt_val; break; case 'b': - if (_track_vec.empty()) + if (_track_vec.size() == 1) global_infos.emplace(opt, opt_val); else track->_b = opt_val; @@ -195,12 +195,12 @@ void SdpParser::load(const string &sdp) { case 'a': { string attr = findSubString(opt_val.data(), nullptr, ":"); if (attr.empty()) { - if (_track_vec.empty()) + if (_track_vec.size() == 1) global_infos.emplace(opt, opt_val); else track->_attr.emplace(opt_val, ""); } else { - if (_track_vec.empty()) + if (_track_vec.size() == 1) global_infos.emplace(opt, opt_val); else track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); @@ -208,7 +208,7 @@ void SdpParser::load(const string &sdp) { break; } default: { - if (_track_vec.empty()) { + if (_track_vec.size() == 1) { global_infos.emplace(opt, opt_val); } else { track->_other[opt] = opt_val; From 098046cb1de1890736d7d7edaf3cd51f506d14b3 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Wed, 26 Jul 2023 16:33:21 +0800 Subject: [PATCH 42/92] =?UTF-8?q?=E6=96=B0=E5=A2=9Ehttp=20api=E4=B8=8Ehttp?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=AE=BF=E9=97=AEip=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E9=99=90=E5=88=B6=E6=9C=BA=E5=88=B6=EF=BC=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=A6=81=E6=AD=A2=E5=85=AC=E7=BD=91=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.ini | 2 ++ server/WebApi.cpp | 5 +++ src/Common/config.cpp | 2 ++ src/Common/config.h | 2 ++ src/Http/HttpFileManager.cpp | 61 ++++++++++++++++++++++++++++++++++-- src/Http/HttpFileManager.h | 7 +++++ 6 files changed, 77 insertions(+), 2 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index f3c51015..9d53457f 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -241,6 +241,8 @@ forbidCacheSuffix= forwarded_ip_header= #默认允许所有跨域请求 allow_cross_domains=1 +#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255 [multicast] #rtp组播截止组播ip地址 diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 6742a957..fe56d404 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -238,6 +238,11 @@ static inline void addHttpListener(){ //该api已被消费 consumed = true; + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + invoker(403, HttpSession::KeyValue(), "Your ip is not allowed to access the service."); + return; + } + if(api_debug){ auto newInvoker = [invoker, parser](int code, const HttpSession::KeyValue &headerOut, const HttpBody::Ptr &body) { //body默认为空 diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 52c79091..d96c939f 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -165,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu"; const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix"; const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header"; const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains"; +const string kAllowIPRange = HTTP_FIELD "allow_ip_range"; static onceToken token([]() { mINI::Instance()[kSendBufSize] = 64 * 1024; @@ -193,6 +194,7 @@ static onceToken token([]() { mINI::Instance()[kForbidCacheSuffix] = ""; mINI::Instance()[kForwardedIpHeader] = ""; mINI::Instance()[kAllowCrossDomains] = 1; + mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255"; }); } // namespace Http diff --git a/src/Common/config.h b/src/Common/config.h index ec546a17..341f1d6c 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -254,6 +254,8 @@ extern const std::string kForbidCacheSuffix; extern const std::string kForwardedIpHeader; // 是否允许所有跨域请求 extern const std::string kAllowCrossDomains; +// 允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +extern const std::string kAllowIPRange; } // namespace Http ////////////SHELL配置/////////// diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 3b0fa544..a6d7b756 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -50,6 +50,58 @@ const string &HttpFileManager::getContentType(const char *name) { return HttpConst::getHttpContentType(name); } +#ifndef ntohll +static uint64_t ntohll(uint64_t val) { + return (((uint64_t)ntohl(val)) << 32) + ntohl(val >> 32); +} +#endif + +static uint64_t get_ip_uint64(const std::string &ip) { + try { + auto storage = SockUtil::make_sockaddr(ip.data(), 0); + if (storage.ss_family == AF_INET) { + return ntohl(reinterpret_cast(reinterpret_cast(storage).sin_addr)); + } + if (storage.ss_family == AF_INET6) { + return ntohll(reinterpret_cast(reinterpret_cast(storage).sin6_addr)); + } + } catch (std::exception &ex) { + WarnL << ex.what(); + } + return 0; +} + +bool HttpFileManager::isIPAllowed(const std::string &ip) { + using IPRangs = std::vector>; + GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs { + IPRangs ret; + auto vec = split(str, ","); + for (auto &item : vec) { + auto range = split(item, "-"); + if (range.size() == 2) { + ret.emplace_back(get_ip_uint64(trim(range[0])), get_ip_uint64(trim(range[1]))); + } else if (range.size() == 1) { + auto ip = get_ip_uint64(trim(range[0])); + ret.emplace_back(ip, ip); + } else { + WarnL << "Invalid ip range: " << item; + } + } + return ret; + }); + + if (allow_ip_range.empty()) { + return true; + } + for (auto &range : allow_ip_range) { + auto ip_int = get_ip_uint64(ip); + if (ip_int >= range.first && ip_int <= range.second) { + return true; + } + } + return false; +} + static string searchIndexFile(const string &dir){ DIR *pDir; dirent *pDirent; @@ -321,10 +373,15 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } - //事件未被拦截,则认为是http下载请求 + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + callback("Your ip is not allowed to access the service.", nullptr); + return; + } + + // 事件未被拦截,则认为是http下载请求 bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); if (!flag) { - //此事件无人监听,我们默认都有权限访问 + // 此事件无人监听,我们默认都有权限访问 callback("", nullptr); } } diff --git a/src/Http/HttpFileManager.h b/src/Http/HttpFileManager.h index 330b9fa1..a3c0c1f3 100644 --- a/src/Http/HttpFileManager.h +++ b/src/Http/HttpFileManager.h @@ -62,6 +62,13 @@ public: * @return mime值 */ static const std::string &getContentType(const char *name); + + /** + * 该ip是否再白名单中 + * @param ip 支持ipv4和ipv6 + */ + static bool isIPAllowed(const std::string &ip); + private: HttpFileManager() = delete; ~HttpFileManager() = delete; From 706c63c64bd438b0fd0f0ab6bc22c9a166812c39 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Wed, 26 Jul 2023 16:40:10 +0800 Subject: [PATCH 43/92] =?UTF-8?q?=E7=A6=81=E6=AD=A2=E9=80=9A=E8=BF=87setSe?= =?UTF-8?q?rverConfig=E6=8E=A5=E5=8F=A3=E4=BF=AE=E6=94=B9ffmpeg.bin?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/FFmpegSource.h | 1 + server/WebApi.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index 68e3c440..8efb7ced 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -20,6 +20,7 @@ namespace FFmpeg { extern const std::string kSnap; + extern const std::string kBin; } class FFmpegSnap { diff --git a/server/WebApi.cpp b/server/WebApi.cpp index fe56d404..04d69246 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -657,6 +657,10 @@ void installWebApi() { continue; #endif } + if (pr.first == FFmpeg::kBin) { + WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api."; + continue; + } if (ini[pr.first] == pr.second) { continue; } From 22e3872cd48c45eb0edf0e6d52483880fdf7cbe6 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Wed, 26 Jul 2023 16:45:47 +0800 Subject: [PATCH 44/92] =?UTF-8?q?=E4=BC=98=E5=8C=96ip=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/HttpFileManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index a6d7b756..fcf61f37 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -93,8 +93,8 @@ bool HttpFileManager::isIPAllowed(const std::string &ip) { if (allow_ip_range.empty()) { return true; } + auto ip_int = get_ip_uint64(ip); for (auto &range : allow_ip_range) { - auto ip_int = get_ip_uint64(ip); if (ip_int >= range.first && ip_int <= range.second) { return true; } From e81efec96ea9008c65f89128119408ad8ed8783b Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Wed, 26 Jul 2023 17:18:33 +0800 Subject: [PATCH 45/92] =?UTF-8?q?ip=E7=99=BD=E5=90=8D=E5=8D=95=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E5=8F=AA=E5=AF=B9=E9=9C=80=E8=A6=81secret=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=9A=84http=20api=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebApi.cpp | 11 ++++------- server/WebApi.h | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 04d69246..11e06ea1 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -238,11 +238,6 @@ static inline void addHttpListener(){ //该api已被消费 consumed = true; - if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { - invoker(403, HttpSession::KeyValue(), "Your ip is not allowed to access the service."); - return; - } - if(api_debug){ auto newInvoker = [invoker, parser](int code, const HttpSession::KeyValue &headerOut, const HttpBody::Ptr &body) { //body默认为空 @@ -593,7 +588,8 @@ void installWebApi() { //获取线程负载 //测试url http://127.0.0.1/index/api/getThreadsLoad - api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = EventPollerPool::Instance().getExecutorLoad(); @@ -611,7 +607,8 @@ void installWebApi() { //获取后台工作线程负载 //测试url http://127.0.0.1/index/api/getWorkThreadsLoad - api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = WorkThreadPool::Instance().getExecutorLoad(); diff --git a/server/WebApi.h b/server/WebApi.h index d4d557f4..673eca11 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -221,14 +221,21 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) { throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \ } -//检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 +// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 +// 同时检测是否在ip白名单内 #define CHECK_SECRET() \ - if(sender.get_peer_ip() != "127.0.0.1"){ \ - CHECK_ARGS("secret"); \ - if(api_secret != allArgs["secret"]){ \ - throw AuthException("secret错误"); \ + do { \ + auto ip = sender.get_peer_ip(); \ + if (!HttpFileManager::isIPAllowed(ip)) { \ + throw AuthException("Your ip is not allowed to access the service."); \ } \ - } + if (ip != "127.0.0.1") { \ + CHECK_ARGS("secret"); \ + if (api_secret != allArgs["secret"]) { \ + throw AuthException("secret错误"); \ + } \ + } \ + } while(false); void installWebApi(); void unInstallWebApi(); From dfae1aee97e9ee36d1fa559ce5ff72baab1cd8ca Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Thu, 27 Jul 2023 15:29:11 +0800 Subject: [PATCH 46/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=BE=E7=BD=AEallow?= =?UTF-8?q?=5Fip=5Frange=E4=B8=BA=E7=A9=BA=E5=90=8E=EF=BC=8C=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=85=B3=E9=97=AD=E9=89=B4=E6=9D=83=E7=9B=B8=E5=85=B3?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/HttpFileManager.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index fcf61f37..cd3ca62c 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -77,12 +77,21 @@ bool HttpFileManager::isIPAllowed(const std::string &ip) { IPRangs ret; auto vec = split(str, ","); for (auto &item : vec) { + if (trim(item).empty()) { + continue; + } auto range = split(item, "-"); if (range.size() == 2) { - ret.emplace_back(get_ip_uint64(trim(range[0])), get_ip_uint64(trim(range[1]))); + auto ip_min = get_ip_uint64(trim(range[0])); + auto ip_max = get_ip_uint64(trim(range[1])); + if (ip_min && ip_max) { + ret.emplace_back(ip_min, ip_max); + } } else if (range.size() == 1) { auto ip = get_ip_uint64(trim(range[0])); - ret.emplace_back(ip, ip); + if (ip) { + ret.emplace_back(ip, ip); + } } else { WarnL << "Invalid ip range: " << item; } From c1f65174714f13e7047868e5c1ba33454d5fabab Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Thu, 27 Jul 2023 15:39:43 +0800 Subject: [PATCH 47/92] =?UTF-8?q?=E5=9C=A8=E5=BC=80=E5=90=AFon=5Fhttp=5Fac?= =?UTF-8?q?cess=20hook=E6=97=B6=EF=BC=8C=E8=AE=BF=E9=97=AEhttp=E6=96=87?= =?UTF-8?q?=E4=BB=B6(=E6=88=96=E7=9B=AE=E5=BD=95)ip=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E6=9C=BA=E5=88=B6=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit on_http_access hook优先级更高,关闭on_http_access hook时才采用ip白名单机制,防止两种鉴权机制间的冲突 --- server/WebHook.cpp | 6 +++++- src/Http/HttpFileManager.cpp | 5 ----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/server/WebHook.cpp b/server/WebHook.cpp index c185444b..23fd7c4a 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -625,7 +625,11 @@ void installWebHook() { if (!hook_enable || hook_http_access.empty()) { // 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权; // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) - invoker("", "", 0); + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + invoker("Your ip is not allowed to access the service.", "", 0); + } else { + invoker("", "", 0); + } return; } diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index cd3ca62c..52f8fabd 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -382,11 +382,6 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } - if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { - callback("Your ip is not allowed to access the service.", nullptr); - return; - } - // 事件未被拦截,则认为是http下载请求 bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); if (!flag) { From 2c8a46862b85f29b9aff35a8ca875f3b6e32d313 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Thu, 27 Jul 2023 15:42:53 +0800 Subject: [PATCH 48/92] =?UTF-8?q?=E6=9D=A5=E8=87=AA127.0.0.1=E7=9A=84http?= =?UTF-8?q?=20api=E8=AF=B7=E6=B1=82=E5=BC=80=E5=90=AFsecret=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 防止http代理越过鉴权 --- postman/ZLMediaKit.postman_collection.json | 82 +++++++++++----------- server/WebApi.h | 8 +-- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index de591c15..bf0ace5f 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -25,7 +25,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -51,7 +51,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -77,7 +77,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -103,7 +103,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -129,7 +129,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -155,7 +155,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "api.apiDebug", @@ -186,7 +186,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -212,7 +212,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -262,7 +262,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -314,7 +314,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -366,7 +366,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -404,7 +404,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "id", @@ -435,7 +435,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -473,7 +473,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -621,7 +621,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -652,7 +652,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -721,7 +721,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -752,7 +752,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "src_url", @@ -809,7 +809,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -839,7 +839,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -885,7 +885,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -931,7 +931,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -977,7 +977,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1028,7 +1028,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1074,7 +1074,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1132,7 +1132,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1178,7 +1178,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1224,7 +1224,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1270,7 +1270,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1316,7 +1316,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "url", @@ -1357,7 +1357,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1388,7 +1388,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "port", @@ -1447,7 +1447,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "dst_url", @@ -1488,7 +1488,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1519,7 +1519,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1555,7 +1555,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1586,7 +1586,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1617,7 +1617,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -1643,7 +1643,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1746,7 +1746,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1834,7 +1834,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", diff --git a/server/WebApi.h b/server/WebApi.h index 673eca11..460d0132 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -229,11 +229,9 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) { if (!HttpFileManager::isIPAllowed(ip)) { \ throw AuthException("Your ip is not allowed to access the service."); \ } \ - if (ip != "127.0.0.1") { \ - CHECK_ARGS("secret"); \ - if (api_secret != allArgs["secret"]) { \ - throw AuthException("secret错误"); \ - } \ + CHECK_ARGS("secret"); \ + if (api_secret != allArgs["secret"]) { \ + throw AuthException("secret错误"); \ } \ } while(false); From 2bcbff09b0ca45b169bf798426eddc738906f87d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Thu, 27 Jul 2023 15:51:07 +0800 Subject: [PATCH 49/92] =?UTF-8?q?web=20hook=E4=B8=8D=E5=86=8D=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E6=94=BE=E8=A1=8C=E6=9D=A5=E8=87=AA127.0.0.1=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E9=89=B4=E6=9D=83=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 防止代理越过鉴权 --- server/WebHook.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 23fd7c4a..a3c8b71e 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -332,7 +332,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { GET_CONFIG(string, hook_publish, Hook::kOnPublish); - if (!hook_enable || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_publish.empty()) { invoker("", ProtocolOption()); return; } @@ -357,7 +357,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { GET_CONFIG(string, hook_play, Hook::kOnPlay); - if (!hook_enable || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_play.empty()) { invoker(""); return; } @@ -371,7 +371,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport); - if (!hook_enable || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_flowreport.empty()) { return; } auto body = make_json(args); @@ -390,7 +390,7 @@ void installWebHook() { // 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm); - if (!hook_enable || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_rtsp_realm.empty()) { // 无需认证 invoker(""); return; @@ -539,7 +539,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) { GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin); - if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_shell_login.empty()) { invoker(""); return; } @@ -617,11 +617,6 @@ void installWebHook() { // 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) { GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess); - if (sender.get_peer_ip() == "127.0.0.1") { - // 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 - invoker("", "", 60 * 60); - return; - } if (!hook_enable || hook_http_access.empty()) { // 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权; // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) From b9af556908d64b024b498e15a220e0356ae6b54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Fri, 28 Jul 2023 14:51:13 +0800 Subject: [PATCH 50/92] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c8ce0a2b..cb657767 100644 --- a/README.md +++ b/README.md @@ -204,9 +204,12 @@ bash build_docker_images.sh ## 联系方式 - 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - - QQ群:两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。 - - 关注微信公众号: + - 请关注微信公众号获取最新消息推送: + + - 也可以自愿有偿加入知识星球咨询和获取资料: + + ## 怎么提问? @@ -214,9 +217,7 @@ bash build_docker_images.sh - 1、仔细看下readme、wiki,如果有必要可以查看下issue. - 2、如果您的问题还没解决,可以提issue. - - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. - - 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)). - - 5、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). + - 3、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). ## 特别感谢 From eaecf9a56ce4f0846ebf255edb379234902b6a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:04:06 +0800 Subject: [PATCH 51/92] =?UTF-8?q?ip=E7=99=BD=E5=90=8D=E5=8D=95=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=B1=80=E5=9F=9F=E7=BD=91ip=2010=E6=AE=B5=20(#2712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.ini | 2 +- src/Common/config.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index 9d53457f..df110b2b 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -242,7 +242,7 @@ forwarded_ip_header= #默认允许所有跨域请求 allow_cross_domains=1 #允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 -allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255 +allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255 [multicast] #rtp组播截止组播ip地址 diff --git a/src/Common/config.cpp b/src/Common/config.cpp index d96c939f..4a742fe9 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -194,7 +194,7 @@ static onceToken token([]() { mINI::Instance()[kForbidCacheSuffix] = ""; mINI::Instance()[kForwardedIpHeader] = ""; mINI::Instance()[kAllowCrossDomains] = 1; - mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255"; + mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255"; }); } // namespace Http From 5464313b41c86f854f9bdbd1a3a88db750bc1984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:04:24 +0800 Subject: [PATCH 52/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dwebrtc=E6=8E=A8?= =?UTF-8?q?=E6=B5=81=E4=BA=92=E6=96=A5=E9=94=81=E9=87=8D=E5=85=A5=E6=AD=BB?= =?UTF-8?q?=E9=94=81bug=20(#2713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit simulcast推流时,在onRecvRtp函数中可能触发对totalReaderCount的调用,从而导致死锁。 --- webrtc/WebRtcPusher.cpp | 4 ++-- webrtc/WebRtcPusher.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 11fb1b30..0a62c0f3 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -61,7 +61,7 @@ bool WebRtcPusher::close(MediaSource &sender) { int WebRtcPusher::totalReaderCount(MediaSource &sender) { auto total_count = _push_src ? _push_src->totalReaderCount() : 0; if (_simulcast) { - std::lock_guard lock(_mtx); + std::lock_guard lock(_mtx); for (auto &src : _push_src_sim) { total_count += src.second->totalReaderCount(); } @@ -99,7 +99,7 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt } } else { //视频 - std::lock_guard lock(_mtx); + std::lock_guard lock(_mtx); auto &src = _push_src_sim[rid]; if (!src) { const auto& stream = _push_src->getMediaTuple().stream; diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index 4c332a35..8d309395 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -67,7 +67,7 @@ private: //推流所有权 std::shared_ptr _push_src_ownership; //推流的rtsp源,支持simulcast - std::mutex _mtx; + std::recursive_mutex _mtx; std::unordered_map _push_src_sim; std::unordered_map > _push_src_sim_ownership; }; From 54194fe501b73dc66928023191278e6b751ffc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:06:36 +0800 Subject: [PATCH 53/92] =?UTF-8?q?=E5=85=BC=E5=AE=B9=E4=B8=8D=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E7=9A=84rtmp=E6=8E=A8=E6=B5=81=E5=99=A8=20(#2417=20#2?= =?UTF-8?q?715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 有些rtmp推流器在服务器返回鉴权成功前就开启推送流数据 --- src/Rtmp/RtmpSession.cpp | 15 ++++++++++++++- src/Rtmp/RtmpSession.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index ebdfb74e..2ebf2f41 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -536,7 +536,14 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { case MSG_AUDIO: case MSG_VIDEO: { if (!_push_src) { - WarnL << "Not a rtmp push!"; + if (_ring_reader) { + throw std::runtime_error("Rtmp player send media packets"); + } + if (packet->isConfigFrame()) { + auto id = packet->type_id; + _push_config_packets.emplace(id, std::move(packet)); + } + WarnL << "Rtmp pusher send media packet before handshake completed!"; return; } @@ -544,6 +551,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { _set_meta_data = true; _push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata()); } + if (!_push_config_packets.empty()) { + for (auto &pr : _push_config_packets) { + _push_src->onWrite(std::move(pr.second)); + } + _push_config_packets.clear(); + } _push_src->onWrite(std::move(packet)); break; } diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index f9c2bf2a..f51b567e 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -97,6 +97,7 @@ private: MediaInfo _media_info; std::weak_ptr _play_src; AMFValue _push_metadata; + std::map _push_config_packets; RtmpMediaSourceImp::Ptr _push_src; std::shared_ptr _push_src_ownership; RtmpMediaSource::RingType::RingReader::Ptr _ring_reader; From 73c8a5faf445eb12fd1a3e91fa8dde17536cda7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:07:22 +0800 Subject: [PATCH 54/92] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=8D=95=E5=AD=97=E8=8A=82=E5=AF=B9=E9=BD=90=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=20(#2716)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __attribute__((packed)) 替换为 #pragma pack(push, 1) --- src/Common/macros.h | 8 -------- src/Extension/H264Rtp.cpp | 6 +----- src/Rtcp/Rtcp.h | 26 +++++++++++--------------- src/Rtcp/RtcpFCI.cpp | 4 ++-- src/Rtcp/RtcpFCI.h | 22 +++++++++++----------- src/Rtmp/Rtmp.h | 19 ++++--------------- src/Rtsp/Rtsp.h | 6 +----- webrtc/RtpExt.cpp | 8 ++------ 8 files changed, 32 insertions(+), 67 deletions(-) diff --git a/src/Common/macros.h b/src/Common/macros.h index 4c61d11c..8c86fc10 100644 --- a/src/Common/macros.h +++ b/src/Common/macros.h @@ -32,14 +32,6 @@ #define __LITTLE_ENDIAN LITTLE_ENDIAN #endif -#ifndef PACKED -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //! defined(_WIN32) -#endif - #ifndef CHECK #define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #endif // CHECK diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index eae8edd1..28c775c6 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -13,9 +13,7 @@ namespace mediakit{ -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class FuFlags { public: @@ -30,11 +28,9 @@ public: unsigned end_bit: 1; unsigned start_bit: 1; #endif -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) H264RtpDecoder::H264RtpDecoder() { _frame = obtainFrame(); diff --git a/src/Rtcp/Rtcp.h b/src/Rtcp/Rtcp.h index 9275506c..3511f4a6 100644 --- a/src/Rtcp/Rtcp.h +++ b/src/Rtcp/Rtcp.h @@ -19,9 +19,7 @@ namespace mediakit { -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) // http://www.networksorcery.com/enp/protocol/rtcp.htm #define RTCP_PT_MAP(XX) \ @@ -235,7 +233,7 @@ private: */ void net2Host(size_t size); -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -272,7 +270,7 @@ private: * 网络字节序转换为主机字节序 */ void net2Host(); -} PACKED; +}; /* * 6.4.1 SR: Sender Report RTCP Packet @@ -371,7 +369,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -441,7 +439,7 @@ private: */ std::string dumpString() const; -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -512,7 +510,7 @@ private: * 网络字节序转换为主机字节序 */ void net2Host(); -} PACKED; +}; // Source description class RtcpSdes : public RtcpHeader { @@ -548,7 +546,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; // https://tools.ietf.org/html/rfc4585#section-6.1 // 6.1. Common Packet Format for Feedback Messages @@ -624,7 +622,7 @@ private: private: static std::shared_ptr create_l(RtcpType type, int fmt, const void *fci, size_t fci_len); -} PACKED; +}; // BYE /* @@ -684,7 +682,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; /* 0 1 2 3 @@ -738,7 +736,7 @@ private: */ void net2Host(size_t size); -} PACKED; +}; /* @@ -777,7 +775,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(); -} PACKED; +}; class RtcpXRDLRR : public RtcpHeader { public: @@ -814,11 +812,9 @@ private: */ void net2Host(size_t size); -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) } // namespace mediakit #endif // ZLMEDIAKIT_RTCP_H diff --git a/src/Rtcp/RtcpFCI.cpp b/src/Rtcp/RtcpFCI.cpp index c6508217..8ed27670 100644 --- a/src/Rtcp/RtcpFCI.cpp +++ b/src/Rtcp/RtcpFCI.cpp @@ -240,7 +240,7 @@ public: RunLengthChunk(SymbolStatus status, uint16_t run_length); // 打印本对象 string dumpString() const; -} PACKED; +}; RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) { type = 0; @@ -291,7 +291,7 @@ public: StatusVecChunk(bool symbol_bit, const vector &status); // 打印本对象 string dumpString() const; -} PACKED; +}; StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector &status) { CHECK(status.size() << symbol_bit <= 14); diff --git a/src/Rtcp/RtcpFCI.h b/src/Rtcp/RtcpFCI.h index 745dc16a..5ce36aa2 100644 --- a/src/Rtcp/RtcpFCI.h +++ b/src/Rtcp/RtcpFCI.h @@ -55,7 +55,7 @@ public: private: uint32_t data; -} PACKED; +}; #if 0 //PSFB fmt = 3 @@ -97,7 +97,7 @@ public: uint8_t padding; static size_t constexpr kSize = 8; -} PACKED; +}; #endif // PSFB fmt = 4 @@ -125,7 +125,7 @@ private: uint32_t ssrc; uint8_t seq_number; uint8_t reserved[3]; -} PACKED; +}; #if 0 //PSFB fmt = 5 @@ -147,7 +147,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; //PSFB fmt = 6 //https://tools.ietf.org/html/rfc5104#section-4.3.2.1 @@ -160,7 +160,7 @@ private: // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ class FCI_TSTN : public FCI_TSTR{ -} PACKED; +}; //PSFB fmt = 7 //https://tools.ietf.org/html/rfc5104#section-4.3.4.1 @@ -183,7 +183,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; #endif @@ -233,7 +233,7 @@ private: // SSRC feedback (32 bits) Consists of one or more SSRC entries which // this feedback message applies to. uint32_t ssrc_feedback[1]; -} PACKED; +}; /////////////////////////////////////////// RTPFB //////////////////////////////////////////////////// @@ -265,7 +265,7 @@ private: uint16_t pid; // bitmask of following lost packets (BLP): 16 bits uint16_t blp; -} PACKED; +}; #if 0 //RTPFB fmt = 3 @@ -300,7 +300,7 @@ private: // to the description in section 4.2.1.2. The value is an // unsigned integer [0..511]. uint32_t max_tbr; -} PACKED; +}; //RTPFB fmt = 4 // https://tools.ietf.org/html/rfc5104#section-4.2.2.1 @@ -314,7 +314,7 @@ private: class FCI_TMMBN : public FCI_TMMBR{ public: -} PACKED; +}; #endif enum class SymbolStatus : uint8_t { @@ -374,7 +374,7 @@ private: uint8_t ref_time[3]; // feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 | uint8_t fb_pkt_count; -} PACKED; +}; } // namespace mediakit #endif // ZLMEDIAKIT_RTCPFCI_H diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index c7fd5262..0e947f72 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -18,13 +18,6 @@ #include "Network/Buffer.h" #include "Extension/Track.h" -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //!defined(_WIN32) - - #define DEFAULT_CHUNK_LEN 128 #define HANDSHAKE_PLAINTEXT 0x03 #define RANDOM_LEN (1536 - 8) @@ -65,9 +58,7 @@ namespace mediakit { -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtmpHandshake { public: @@ -81,7 +72,7 @@ public: void create_complex_c0c1(); -}PACKED; +}; class RtmpHeader { public: @@ -97,7 +88,7 @@ public: uint8_t body_size[3]; uint8_t type_id; uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */ -}PACKED; +}; class FLVHeader { public: @@ -130,7 +121,7 @@ public: uint32_t length; //固定为0 uint32_t previous_tag_size0; -} PACKED; +}; class RtmpTagHeader { public: @@ -139,11 +130,9 @@ public: uint8_t timestamp[3] = {0}; uint8_t timestamp_ex = 0; uint8_t streamid[3] = {0}; /* Always 0. */ -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) class RtmpPacket : public toolkit::Buffer{ public: diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 55e8ee5a..01817315 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -65,9 +65,7 @@ typedef enum { }; // namespace Rtsp -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtpHeader { public: @@ -132,11 +130,9 @@ private: size_t getPayloadOffset() const; // 返回padding长度 size_t getPaddingSize(size_t rtp_size) const; -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) // 此rtp为rtp over tcp形式,需要忽略前4个字节 class RtpPacket : public toolkit::BufferRaw { diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index dda65077..b7d97b5c 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -11,9 +11,7 @@ #include "RtpExt.h" #include "Sdp.h" -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) using namespace std; using namespace toolkit; @@ -51,7 +49,7 @@ private: uint8_t id: 4; #endif uint8_t data[1]; -} PACKED; +}; //0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -77,11 +75,9 @@ private: uint8_t id; uint8_t len; uint8_t data[1]; -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) ////////////////////////////////////////////////////////////////// From 00b3c5184a3b488dc43df65a6be1685b87036356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:24:07 +0800 Subject: [PATCH 55/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8DRtpProcess=E6=9E=90?= =?UTF-8?q?=E6=9E=84=E4=B8=AD=E6=8A=9B=E5=BC=82=E5=B8=B8=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98=20(#2714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WarnP(this) 时会调用get_peer_ip()接口,此接口可能抛异常; 析构中抛异常可导致程序直接退出。 --- src/Rtp/RtpProcess.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index 741cc4b1..0ca3f704 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -210,31 +210,27 @@ void RtpProcess::setOnDetach(function cb) { } string RtpProcess::get_peer_ip() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::"; + } catch (std::exception &ex) { return "::"; } - return SockUtil::inet_ntoa((sockaddr *)_addr.get()); } uint16_t RtpProcess::get_peer_port() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0; + } catch (std::exception &ex) { return 0; } - return SockUtil::inet_port((sockaddr *)_addr.get()); } string RtpProcess::get_local_ip() { - if (_sock) { - return _sock->get_local_ip(); - } - return "::"; + return _sock ? _sock->get_local_ip() : "::"; } uint16_t RtpProcess::get_local_port() { - if (_sock) { - return _sock->get_local_port(); - } - return 0; + return _sock ? _sock->get_local_port() : 0; } string RtpProcess::getIdentifier() const { @@ -305,4 +301,4 @@ float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { } }//namespace mediakit -#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file +#endif//defined(ENABLE_RTPPROXY) From bd8ad2eabfc001ade387793072769366a2814707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 13:24:21 +0800 Subject: [PATCH 56/92] =?UTF-8?q?=E4=BC=98=E5=8C=96enhanced-rtmp=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E6=80=A7=E8=83=BD=20(#2717)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtmp/Rtmp.cpp | 67 ++++++++++++++++++++++++++++++++--------------- src/Rtmp/Rtmp.h | 6 +++++ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 8dd7505d..95fe8fe7 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -257,50 +257,75 @@ void RtmpHandshake::random_generate(char *bytes, int size) { } } +#pragma pack(push, 1) + +struct RtmpVideoHeaderEnhanced { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t enhanced : 1; + uint8_t frame_type : 3; + uint8_t pkt_type : 4; + uint32_t fourcc; +#else + uint8_t pkt_type : 4; + uint8_t frame_type : 3; + uint8_t enhanced : 1; + uint32_t fourcc; +#endif +}; + +struct RtmpVideoHeaderClassic { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t frame_type : 4; + uint8_t codec_id : 4; + uint8_t h264_pkt_type; +#else + uint8_t codec_id : 4; + uint8_t frame_type : 4; + uint8_t h264_pkt_type; +#endif +}; + +#pragma pack(pop) + CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) { RtmpPacketInfo save; info = info ? info : &save; info->codec = CodecInvalid; CHECK(size > 0); - if (data[0] >> 7) { + RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data; + if (enhanced_header->enhanced) { // IsExHeader == 1 CHECK(size >= 5, "Invalid rtmp buffer size: ", size); info->is_enhanced = true; - info->video.frame_type = (RtmpFrameType)((data[0] >> 4) & 0x07); - info->video.pkt_type = (RtmpPacketType)(data[0] & 0x0f); - if (memcmp(data + 1, "av01", 4) == 0) { - // AV1 - info->codec = CodecAV1; - } else if (memcmp(data + 1, "vp09", 4) == 0) { - // VP9 - info->codec = CodecVP9; - } else if (memcmp(data + 1, "hvc1", 4) == 0) { - // HEVC(H265) - info->codec = CodecH265; - } else { - WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); + info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type); + info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type); + + switch (ntohl(enhanced_header->fourcc)) { + case fourcc_av1: info->codec = CodecAV1; break; + case fourcc_vp9: info->codec = CodecVP9; break; + case fourcc_hevc: info->codec = CodecH265; break; + default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); } } else { // IsExHeader == 0 + RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data; info->is_enhanced = false; - info->video.frame_type = (RtmpFrameType)(data[0] >> 4); - auto rtmp_codec = (RtmpVideoCodec)(data[0] & 0x0f); - - switch (rtmp_codec) { + info->video.frame_type = (RtmpFrameType)(classic_header->frame_type); + switch ((RtmpVideoCodec)(classic_header->codec_id)) { case RtmpVideoCodec::h264: { CHECK(size >= 1, "Invalid rtmp buffer size: ", size); info->codec = CodecH264; - info->video.h264_pkt_type = (RtmpH264PacketType)data[1]; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; break; } case RtmpVideoCodec::h265: { CHECK(size >= 1, "Invalid rtmp buffer size: ", size); info->codec = CodecH265; - info->video.h264_pkt_type = (RtmpH264PacketType)data[1]; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; break; } - default: WarnL << "Rtmp video codec not supported: " << (int)rtmp_codec; break; + default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break; } } return info->codec; diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 0e947f72..28ba0ebe 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -357,6 +357,12 @@ struct RtmpPacketInfo { }; }; // https://github.com/veovera/enhanced-rtmp + +// 增强型rtmp FourCC +static constexpr uint32_t fourcc_vp9 = 'vp09'; +static constexpr uint32_t fourcc_av1 = 'av01'; +static constexpr uint32_t fourcc_hevc = 'hvc1'; + CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); }//namespace mediakit From 5a2bf8d19678db1965e6f4c74b9af9d89af5e2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 23:03:28 +0800 Subject: [PATCH 57/92] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AF=B9enhanced=20rtm?= =?UTF-8?q?p=20videocodecid=E7=9A=84=E5=85=BC=E5=AE=B9=20(#2718)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 请查阅: https://github.com/veovera/enhanced-rtmp/issues/8 --- src/Extension/Factory.cpp | 3 +++ src/Rtmp/Rtmp.cpp | 8 ++++---- src/Rtmp/Rtmp.h | 13 ++++++------- src/Rtmp/RtmpDemuxer.cpp | 20 ++++---------------- src/Rtmp/RtmpDemuxer.h | 2 -- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index a19d0f66..63a2bbd5 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -204,7 +204,10 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){ auto type_id = (RtmpVideoCodec)val.as_integer(); switch (type_id) { case RtmpVideoCodec::h264: return CodecH264; + case RtmpVideoCodec::fourcc_hevc: case RtmpVideoCodec::h265: return CodecH265; + case RtmpVideoCodec::fourcc_av1: return CodecAV1; + case RtmpVideoCodec::fourcc_vp9: return CodecVP9; default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid; } } diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 95fe8fe7..140afeed 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -301,10 +301,10 @@ CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *i info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type); info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type); - switch (ntohl(enhanced_header->fourcc)) { - case fourcc_av1: info->codec = CodecAV1; break; - case fourcc_vp9: info->codec = CodecVP9; break; - case fourcc_hevc: info->codec = CodecH265; break; + switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) { + case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break; + case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break; + case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break; default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); } } else { diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 28ba0ebe..30a56c74 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -264,7 +264,7 @@ enum class RtmpFrameType : uint8_t { }; // UB [4]; Codec Identifier. -enum class RtmpVideoCodec : uint8_t { +enum class RtmpVideoCodec : uint32_t { h263 = 2, // Sorenson H.263 screen_video = 3, // Screen video vp6 = 4, // On2 VP6 @@ -272,6 +272,11 @@ enum class RtmpVideoCodec : uint8_t { screen_video2 = 6, // Screen video version 2 h264 = 7, // avc h265 = 12, // 国内扩展 + + // 增强型rtmp FourCC + fourcc_vp9 = 'vp09', + fourcc_av1 = 'av01', + fourcc_hevc = 'hvc1' }; // UI8; @@ -357,12 +362,6 @@ struct RtmpPacketInfo { }; }; // https://github.com/veovera/enhanced-rtmp - -// 增强型rtmp FourCC -static constexpr uint32_t fourcc_vp9 = 'vp09'; -static constexpr uint32_t fourcc_av1 = 'av01'; -static constexpr uint32_t fourcc_hevc = 'hvc1'; - CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); }//namespace mediakit diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index e591acbf..20d8f052 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -75,19 +75,13 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val) { } if (key == "videodatarate") { videodatarate = val.as_integer(); - _videodatarate = videodatarate * 1024; return; } }); if (videocodecid) { // 有视频 ret = true; - if (videocodecid->type() == AMF_NUMBER && videocodecid->as_integer() == (int)RtmpVideoCodec::h264) { - // https://github.com/veovera/enhanced-rtmp/issues/8 - _complete_delay = true; - } else { - makeVideoTrack(*videocodecid, videodatarate * 1024); - } + makeVideoTrack(*videocodecid, videodatarate * 1024); } if (audiocodecid) { // 有音频 @@ -98,7 +92,7 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val) { WarnL << ex.what(); } - if (ret && !_complete_delay) { + if (ret) { // metadata中存在track相关的描述,那么我们根据metadata判断有多少个track addTrackCompleted(); } @@ -114,14 +108,8 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_VIDEO: { if (!_try_get_video_track) { _try_get_video_track = true; - RtmpPacketInfo info; - auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &info); - if (codec_id != CodecInvalid) { - makeVideoTrack(Factory::getTrackByCodecId(codec_id), _videodatarate); - if (_complete_delay) { - addTrackCompleted(); - } - } + auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size()); + makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0); } if (_video_rtmp_decoder) { _video_rtmp_decoder->inputRtmp(pkt); diff --git a/src/Rtmp/RtmpDemuxer.h b/src/Rtmp/RtmpDemuxer.h index df810652..4cf0412b 100644 --- a/src/Rtmp/RtmpDemuxer.h +++ b/src/Rtmp/RtmpDemuxer.h @@ -51,9 +51,7 @@ private: private: bool _try_get_video_track = false; bool _try_get_audio_track = false; - bool _complete_delay = false; float _duration = 0; - int _videodatarate = 0; AudioTrack::Ptr _audio_track; VideoTrack::Ptr _video_track; RtmpCodec::Ptr _audio_rtmp_decoder; From 63a50104fadc56c9cf02e3e9e12b2081e3229a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 23:04:26 +0800 Subject: [PATCH 58/92] =?UTF-8?q?rtp=20ntp=E6=97=B6=E9=97=B4=E6=88=B3?= =?UTF-8?q?=E9=87=87=E7=94=A8=E9=80=8F=E4=BC=A0=E6=96=B9=E5=BC=8F=20(#2719?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtsp/RtpCodec.cpp | 2 +- src/Rtsp/RtspMuxer.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Rtsp/RtpCodec.cpp b/src/Rtsp/RtpCodec.cpp index 21257bce..6d1d4a9c 100644 --- a/src/Rtsp/RtpCodec.cpp +++ b/src/Rtsp/RtpCodec.cpp @@ -39,7 +39,7 @@ RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bo ++_seq; header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000); header->ssrc = htonl(_ssrc); - + rtp->ntp_stamp = stamp; //有效负载 if (data) { memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len); diff --git a/src/Rtsp/RtspMuxer.cpp b/src/Rtsp/RtspMuxer.cpp index 755b7f5a..6f5e4f8f 100644 --- a/src/Rtsp/RtspMuxer.cpp +++ b/src/Rtsp/RtspMuxer.cpp @@ -14,9 +14,12 @@ using namespace std; using namespace toolkit; +#define ENABLE_NTP_STAMP 0 + namespace mediakit { void RtspMuxer::onRtp(RtpPacket::Ptr in, bool is_key) { +#if ENABLE_NTP_STAMP if (_live) { if (_rtp_stamp[in->type] != in->getHeader()->stamp) { //rtp时间戳变化才计算ntp,节省cpu资源 @@ -34,6 +37,7 @@ void RtspMuxer::onRtp(RtpPacket::Ptr in, bool is_key) { //点播情况下设置ntp时间戳为rtp时间戳加基准ntp时间戳 in->ntp_stamp = _ntp_stamp_start + (in->getStamp() * uint64_t(1000) / in->sample_rate); } +#endif _rtpRing->write(std::move(in), is_key); } @@ -49,7 +53,10 @@ RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title) { _rtpInterceptor->setDelegate(std::make_shared([this](RtpPacket::Ptr in, bool is_key) { onRtp(std::move(in), is_key); })); + +#if ENABLE_NTP_STAMP _ntp_stamp_start = getCurrentMillisecond(true); +#endif } bool RtspMuxer::addTrack(const Track::Ptr &track) { @@ -75,10 +82,12 @@ bool RtspMuxer::addTrack(const Track::Ptr &track) { } void RtspMuxer::trySyncTrack() { +#if ENABLE_NTP_STAMP if (_encoder[TrackAudio] && _encoder[TrackVideo]) { //音频时间戳同步于视频,因为音频时间戳被修改后不影响播放 _stamp[TrackAudio].syncTo(_stamp[TrackVideo]); } +#endif } bool RtspMuxer::inputFrame(const Frame::Ptr &frame) { From bd7982ecc1e5394adcf8a73eff7c6bdf8f9e73d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 29 Jul 2023 23:52:17 +0800 Subject: [PATCH 59/92] =?UTF-8?q?=E6=9F=90=E4=BA=9B=E5=88=86=E6=94=AF?= =?UTF-8?q?=E4=B8=8D=E5=BC=80=E5=90=AFdocker=20ci=20(#2722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为了加快编译速度,只开启master、feature、release分支的docker ci --- .github/workflows/docker.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index db67603d..c18493ac 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,17 @@ name: Docker -on: [push, pull_request] +on: + push: + branches: + - "master" + - "feature/*" + - "release/*" + + pull_request: + branches: + - "master" + - "feature/*" + - "release/*" env: # Use docker.io for Docker Hub if empty From 2935bc1932a5bf50ce2dc0479dd704b1c62d77d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 5 Aug 2023 09:24:52 +0800 Subject: [PATCH 60/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dsdp=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9C=AA=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#2739)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决rtsp拉流代理时无法区分直播点播问题 --- src/Rtsp/Rtsp.cpp | 54 ++++++++++------------------------------------- src/Rtsp/Rtsp.h | 1 - 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index f4963531..b5b50360 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -149,7 +149,6 @@ static TrackType toTrackType(const string &str) { } void SdpParser::load(const string &sdp) { - std::multimap global_infos; { _track_vec.clear(); SdpTrack::Ptr track = std::make_shared(); @@ -166,23 +165,17 @@ void SdpParser::load(const string &sdp) { string opt_val = line.substr(2); switch (opt) { case 't': - if (_track_vec.size() == 1) - global_infos.emplace(opt, opt_val); - else - track->_t = opt_val; + track->_t = opt_val; break; case 'b': - if (_track_vec.size() == 1) - global_infos.emplace(opt, opt_val); - else - track->_b = opt_val; + track->_b = opt_val; break; case 'm': { track = std::make_shared(); int pt, port, port_count; - char rtp[16] = { 0 }, type[16]; - if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt) - || 5 == sscanf(opt_val.data(), " %15[^ ] %d/%d %15[^ ] %d", type, &port, &port_count, rtp, &pt)) { + char rtp[16] = {0}, type[16]; + if (4 == sscanf(opt_val.data(), " %15[^ ] %d %15[^ ] %d", type, &port, rtp, &pt) || + 5 == sscanf(opt_val.data(), " %15[^ ] %d/%d %15[^ ] %d", type, &port, &port_count, rtp, &pt)) { track->_pt = pt; track->_samplerate = RtpPayload::getClockRate(pt); track->_channel = RtpPayload::getAudioChannel(pt); @@ -195,43 +188,17 @@ void SdpParser::load(const string &sdp) { case 'a': { string attr = findSubString(opt_val.data(), nullptr, ":"); if (attr.empty()) { - if (_track_vec.size() == 1) - global_infos.emplace(opt, opt_val); - else - track->_attr.emplace(opt_val, ""); + track->_attr.emplace(opt_val, ""); } else { - if (_track_vec.size() == 1) - global_infos.emplace(opt, opt_val); - else - track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); - } - break; - } - default: { - if (_track_vec.size() == 1) { - global_infos.emplace(opt, opt_val); - } else { - track->_other[opt] = opt_val; + track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); } break; } + default: track->_other[opt] = opt_val; break; } } } - for (auto &info : global_infos) { - std::string attr; - switch (info.first) { - case 'a': - attr = findSubString(info.second.data(), nullptr, ":"); - if (attr == "control") { - _control_url = findSubString(info.second.data(), ":", nullptr); - } - break; - - default: break; - } - } for (auto &track_ptr : _track_vec) { auto &track = *track_ptr; auto it = track._attr.find("range"); @@ -355,9 +322,10 @@ string SdpParser::toString() const { } std::string SdpParser::getControlUrl(const std::string &url) const { - if (_control_url.find("://") != string::npos) { + auto title_track = getTrack(TrackTitle); + if (title_track && title_track->_control.find("://") != string::npos) { // 以rtsp://开头 - return _control_url; + return title_track->_control; } return url; } diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 01817315..dc34f918 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -243,7 +243,6 @@ public: private: std::vector _track_vec; - std::string _control_url; }; /** From ff225114ec64756654f1c535515fdd78e5fedc7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 5 Aug 2023 09:25:35 +0800 Subject: [PATCH 61/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drtsp=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E6=97=A0=E6=B3=95=E5=8F=91=E9=80=81rtp?= =?UTF-8?q?=E7=BB=99nat=E5=86=85=E6=92=AD=E6=94=BE=E5=99=A8=E7=9A=84bug=20?= =?UTF-8?q?(#2737=20#2740)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在udp connect rtsp播放器内网端口后,可能导致过滤掉其公网端口发送的打洞包; 从未无法完成与rtsp播放器udp端口的双向通信。 Socket::bindPeerAddr修改成软绑定时,只是保存发送目标地址,不会导致打洞包的过滤。 --- 3rdpart/ZLToolKit | 2 +- src/Rtsp/RtspSession.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 79db405b..d2016522 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 79db405ba43c29453c60c3e054d863ce6bd1ef29 +Subproject commit d2016522a0e4b1d8df51a78b7415fe148f7245ca diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index e9c69ac2..a660e6be 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -721,11 +721,11 @@ void RtspSession::handleReq_Setup(const Parser &parser) { auto peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtpPort); //设置rtp发送目标地址 - pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //设置rtcp发送目标地址 peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtcpPort); - pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //尝试获取客户端nat映射地址 startListenPeerUdpData(trackIdx); From 98a3b8ab912ea97ccad7c01b2e947c4f745f9bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 5 Aug 2023 09:26:52 +0800 Subject: [PATCH 62/92] =?UTF-8?q?=E6=8E=A8=E6=B5=81=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=8F=91=E9=80=81eof=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E4=BF=A1=E4=BB=A4=E7=BB=99rtmp=E6=92=AD=E6=94=BE=E5=99=A8=20(#?= =?UTF-8?q?2711=20#2723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtmp/RtmpSession.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 2ebf2f41..8a5d8c79 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -327,6 +327,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr if (!strong_self) { return; } + strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA); strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); }); src->pause(false); From fbf4819c5a47f3fc9f318a35d363b8af71c4d606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 5 Aug 2023 21:29:34 +0800 Subject: [PATCH 63/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=88=E4=BD=9C?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 +++++++++++++++++++++++++------------------- README_en.md | 2 ++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index cb657767..7def21a2 100644 --- a/README.md +++ b/README.md @@ -169,30 +169,36 @@ bash build_docker_images.sh ``` ## 合作项目 + + - 视频管理平台 + - [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台 + - [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台 + - [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器 + - [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器 + - [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器 + + - 播放器 + - [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器,支持本项目多种专属协议 + - [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器 + - [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器 + - [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器 - - 可视化管理网站 - - [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR) - - [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI) - - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) - - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - - - 流媒体管理平台 - - [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro) - - [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream) - - [Go实现的GB28181服务器](https://github.com/panjjo/gosip) - - [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) - - [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome) - - - 客户端 - - [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen) +- WEB管理网站 + - [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放 + + - SDK + - [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库 + - [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk + + - 其他项目(已停止更新) + - [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) + - [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI) + - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) + - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo) - [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi) - [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk) - - 播放器 - - [基于wasm支持H265的播放器](https://github.com/numberwolf/h265web.js) - - [基于MSE的websocket-fmp4播放器](https://github.com/v354412101/wsPlayer) - - [全国产webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) ## 授权协议 diff --git a/README_en.md b/README_en.md index 1d83966d..c81b6b87 100644 --- a/README_en.md +++ b/README_en.md @@ -350,6 +350,7 @@ bash build_docker_images.sh - Media management platform - [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro) - [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream) + - [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer) - [GB28181 server implemented in Go](https://github.com/panjjo/gosip) - [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http) - [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome) @@ -364,6 +365,7 @@ bash build_docker_images.sh - [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js) - [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer) - [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) + - [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player) ## License From 383da1e09ea78c24f380ffce8b7695ceec315c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sat, 5 Aug 2023 21:30:01 +0800 Subject: [PATCH 64/92] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=20(#?= =?UTF-8?q?2741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除无效的配置项, 同时修复编译警告 --- src/Common/config.cpp | 2 -- src/Rtmp/Rtmp.h | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 4a742fe9..87e4e855 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -231,12 +231,10 @@ static onceToken token([]() { ////////////RTMP服务器配置/////////// namespace Rtmp { #define RTMP_FIELD "rtmp." -const string kModifyStamp = RTMP_FIELD "modifyStamp"; const string kHandshakeSecond = RTMP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = false; mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; }); diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 30a56c74..66908af2 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -263,6 +263,8 @@ enum class RtmpFrameType : uint8_t { video_info_frame = 5, // video info/command frame }; +#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + // UB [4]; Codec Identifier. enum class RtmpVideoCodec : uint32_t { h263 = 2, // Sorenson H.263 @@ -274,9 +276,9 @@ enum class RtmpVideoCodec : uint32_t { h265 = 12, // 国内扩展 // 增强型rtmp FourCC - fourcc_vp9 = 'vp09', - fourcc_av1 = 'av01', - fourcc_hevc = 'hvc1' + fourcc_vp9 = MKBETAG('v', 'p', '0', '9'), + fourcc_av1 = MKBETAG('a', 'v', '0', '1'), + fourcc_hevc = MKBETAG('h', 'v', 'c', '1') }; // UI8; From 2378617dd8bb208129ae7cbbafc26dfeae096d13 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Tue, 8 Aug 2023 16:01:01 +0800 Subject: [PATCH 65/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=89=8D=E5=90=91?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E5=AF=BC=E8=87=B4=E7=9A=84=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/MediaSource.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index fc1d64f4..c0757bf3 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -15,8 +15,10 @@ #include "MediaSource.h" #include "Common/config.h" #include "Common/Parser.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Record/MP4Reader.h" #include "PacketCache.h" + using namespace std; using namespace toolkit; From a7d95461ee9f8c898d857103eb39049d0c652398 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 12 Aug 2023 15:24:20 +0800 Subject: [PATCH 66/92] =?UTF-8?q?=E8=BF=87=E6=BB=A4=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9A=84webrtc=20rtp=20ext=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E9=AB=98webrtc=E8=BD=AC=E5=8F=91=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在测试obs simulcast推流时,发现chrome无法正常播放, 分析rtp ext扩展,发现是rtp mid ext未过滤导致, zlmediakit在回复answer sdp时,已申明不支持mid扩展, 但是obs并未理会还是发送mid扩展。 根据answer sdp过滤rtp ext可兼容此问题。 --- webrtc/WebRtcTransport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 031710e8..127a1987 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -515,7 +515,7 @@ void WebRtcTransportImp::onStartWebRTC() { _pt_to_track.emplace(track->plan_rtx->pt, std::unique_ptr(new WrappedRtxTrack(track))); } // 记录rtp ext类型与id的关系,方便接收或发送rtp时修改rtp ext id - track->rtp_ext_ctx = std::make_shared(*m_offer); + track->rtp_ext_ctx = std::make_shared(m_answer); weak_ptr weak_track = track; track->rtp_ext_ctx->setOnGetRtp([this, weak_track](uint8_t pt, uint32_t ssrc, const string &rid) { // ssrc --> MediaTrack From aac890561985ddb703ce4c102f86c63ec0f5c6bb Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 12 Aug 2023 15:43:30 +0800 Subject: [PATCH 67/92] =?UTF-8?q?api=20secret=E6=97=A0=E6=95=88=E6=97=B6?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=9B=B4=E6=8E=A5=E9=80=80=E5=87=BA=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 而是修改为随机字符串并持久化至配置文件 --- server/main.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/main.cpp b/server/main.cpp index b9d84249..f0d0ce1b 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -351,10 +351,13 @@ int start_main(int argc,char *argv[]) { #endif //defined(ENABLE_SRT) try { - auto secret = mINI::Instance()[API::kSecret]; + auto &secret = mINI::Instance()[API::kSecret]; if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) { // 使用默认secret被禁止启动 - throw std::invalid_argument("please modify the configuration named " + API::kSecret + " in " + g_ini_file); + secret = makeRandStr(32, true); + mINI::Instance().dumpFile(g_ini_file); + WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret + << ", saved config file: " << g_ini_file; } //rtsp服务器,端口默认554 if (rtspPort) { rtspSrv->start(rtspPort); } From 08299b66521fc25b8461a85e6c1d677913c9ee32 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Tue, 8 Aug 2023 14:24:45 +0800 Subject: [PATCH 68/92] =?UTF-8?q?mp4=E5=BD=95=E5=88=B6=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/MP4Recorder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index 4784fcda..ade1d519 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -54,6 +54,7 @@ void MP4Recorder::createFile() { try { _muxer = std::make_shared(); + TraceL << "Open tmp mp4 file: " << full_path_tmp; _muxer->openMP4(full_path_tmp); for (auto &track :_tracks) { //添加track @@ -71,10 +72,13 @@ void MP4Recorder::asyncClose() { auto full_path_tmp = _full_path_tmp; auto full_path = _full_path; auto info = _info; + TraceL << "Start close tmp mp4 file: " << full_path_tmp; WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable { info.time_len = muxer->getDuration() / 1000.0f; // 关闭mp4可能非常耗时,所以要放在后台线程执行 + TraceL << "Closing tmp mp4 file: " << full_path_tmp; muxer->closeMP4(); + TraceL << "Closed tmp mp4 file: " << full_path_tmp; if (!full_path_tmp.empty()) { // 获取文件大小 info.file_size = File::fileSize(full_path_tmp.data()); @@ -86,6 +90,7 @@ void MP4Recorder::asyncClose() { // 临时文件名改成正式文件名,防止mp4未完成时被访问 rename(full_path_tmp.data(), full_path.data()); } + TraceL << "Emit mp4 record event: " << full_path; //触发mp4录制切片生成事件 NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info); }); From 2e05119df12ba0721a2b7061dba55187569d891b Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Tue, 8 Aug 2023 14:24:01 +0800 Subject: [PATCH 69/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=BF=E9=97=AEhttp?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B=E7=B4=A2=E5=BC=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/HttpFileManager.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 52f8fabd..3ec4d40c 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -119,7 +119,7 @@ static string searchIndexFile(const string &dir){ } set setFile; while ((pDirent = readdir(pDir)) != NULL) { - static set indexSet = {"index.html", "index.htm", "index"}; + static set indexSet = {"index.html", "index.htm"}; if (indexSet.find(pDirent->d_name) != indexSet.end()) { string ret = pDirent->d_name; closedir(pDir); @@ -571,11 +571,14 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi if (File::is_dir(file_path.data())) { auto indexFile = searchIndexFile(file_path); if (!indexFile.empty()) { - //发现该文件夹下有index文件 + // 发现该文件夹下有index文件 file_path = pathCat(file_path, indexFile); - parser.setUrl(pathCat(parser.url(), indexFile)); - accessFile(sender, parser, media_info, file_path, cb); - return; + if (!File::is_dir(file_path.data())) { + // 不是文件夹 + parser.setUrl(pathCat(parser.url(), indexFile)); + accessFile(sender, parser, media_info, file_path, cb); + return; + } } string strMenu; //生成文件夹菜单索引 From f69204b3218eef14f5a8546de86709367ea173e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Sun, 13 Aug 2023 20:46:08 +0800 Subject: [PATCH 70/92] =?UTF-8?q?hook=E8=AF=B7=E6=B1=82=E6=B7=BB=E5=8A=A0h?= =?UTF-8?q?ook=5Findex,=20=E9=98=B2=E6=AD=A2hook=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=B9=B1=E5=BA=8F=20(#2758)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebHook.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/WebHook.cpp b/server/WebHook.cpp index a3c8b71e..72ddb7fb 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -164,12 +164,16 @@ string getVhost(const HttpArgs &value) { return val != value.end() ? val->second : ""; } +static atomic s_hook_index { 0 }; + void do_http_hook(const string &url, const ArgsType &body, const function &func, uint32_t retry) { GET_CONFIG(string, mediaServerId, General::kMediaServerId); GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec); GET_CONFIG(float, retry_delay, Hook::kRetryDelay); const_cast(body)["mediaServerId"] = mediaServerId; + const_cast(body)["hook_index"] = s_hook_index++; + auto requester = std::make_shared(); requester->setMethod("POST"); auto bodyStr = to_string(body); From e628cba1ca644f1c56f34a71b4280ea4464aa957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E6=A5=9A?= <771730766@qq.com> Date: Mon, 14 Aug 2023 00:05:32 +0800 Subject: [PATCH 71/92] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B9=E8=BF=87=E6=BB=A4on=5Fstream=5Fchanged=20hook?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=B1=BB=E5=9E=8B=20(#2759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配置项hook.stream_changed_schemas可指定监听感兴趣的协议 --- conf/config.ini | 2 ++ server/WebHook.cpp | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index df110b2b..0bd86b0b 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -166,6 +166,8 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm on_shell_login=https://127.0.0.1/index/hook/on_shell_login #直播流注册或注销事件 on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed +#过滤on_stream_changed hook的协议类型,可以选择只监听某些感兴趣的协议;置空则不过滤协议 +stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 #无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用 on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader #播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流 diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 72ddb7fb..d05c4f60 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report"; const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm"; const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth"; const string kOnStreamChanged = HOOK_FIELD "on_stream_changed"; +const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas"; const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found"; const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4"; const string kOnRecordTs = HOOK_FIELD "on_record_ts"; @@ -76,6 +77,7 @@ static onceToken token([]() { mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; mINI::Instance()[kRetryDelay] = 3.0; + mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4"; }); } // namespace Hook @@ -442,10 +444,26 @@ void installWebHook() { // 监听rtsp、rtmp源注册或注销事件 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged); - if (!hook_enable || hook_stream_chaned.empty()) { + GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged); + if (!hook_enable || hook_stream_changed.empty()) { return; } + GET_CONFIG_FUNC(std::set, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) { + std::set ret; + auto vec = split(str, "/"); + for (auto &schema : vec) { + trim(schema); + if (!schema.empty()) { + ret.emplace(schema); + } + } + return ret; + }); + if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) { + // 该协议注册注销事件被忽略 + return; + } + ArgsType body; if (bRegist) { body = makeMediaSourceJson(sender); @@ -456,7 +474,7 @@ void installWebHook() { body["regist"] = bRegist; } // 执行hook - do_http_hook(hook_stream_chaned, body, nullptr); + do_http_hook(hook_stream_changed, body, nullptr); }); GET_CONFIG_FUNC(vector, origin_urls, Cluster::kOriginUrl, [](const string &str) { From 90c164f7f774ba6186931fdc1dc995c09f8fb55a Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 20 Aug 2023 11:19:57 +0800 Subject: [PATCH 72/92] =?UTF-8?q?rtsp=E6=92=AD=E6=94=BE=E5=99=A8=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E9=9D=9E=E6=B3=95=E7=9A=84=E5=9B=9E=E5=A4=8D=20(#2760?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 提高对一些rtsp流的兼容性 --- src/Rtsp/RtspPlayer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 599677ec..676d89b1 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -483,6 +483,11 @@ void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { } void RtspPlayer::onWholeRtspPacket(Parser &parser) { + if (!start_with(parser.method(), "RTSP")) { + // 不是rtsp回复,忽略 + WarnL << "Not rtsp response: " << parser.method(); + return; + } try { decltype(_on_response) func; _on_response.swap(func); From 0c80f0c13c9a2c33bded1d93eeafbf11fe5b097d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 20 Aug 2023 11:22:37 +0800 Subject: [PATCH 73/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0zltoolkit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复在收到tcp reset时,获取socket对端地址失败的问题: #2749 --- 3rdpart/ZLToolKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index d2016522..bb49f04d 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit d2016522a0e4b1d8df51a78b7415fe148f7245ca +Subproject commit bb49f04deeec40eb4331f6e317fd2164af6a7d95 From 895e93cb6aae82f9fd6f19b0980c28062b6b9d2f Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 20 Aug 2023 12:07:04 +0800 Subject: [PATCH 74/92] =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E6=B1=A0=E5=88=86?= =?UTF-8?q?=E9=85=8D=E7=AB=AF=E5=8F=A3=E6=94=B9=E6=88=90=E6=97=A0=E5=BA=8F?= =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决zlmediakit重启后端口重复分配导致国标串流问题 --- src/Rtsp/Rtsp.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index b5b50360..579a881e 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -8,12 +8,13 @@ * may be found in the AUTHORS file in the root of the source tree. */ +#include +#include +#include #include "Rtsp.h" #include "Common/Parser.h" #include "Common/config.h" #include "Network/Socket.h" -#include -#include using namespace std; using namespace toolkit; @@ -392,9 +393,13 @@ public: private: void setRange(uint16_t start_pos, uint16_t end_pos) { + std::mt19937 rng(std::random_device {}()); lock_guard lck(_pool_mtx); + auto it = _port_pair_pool.begin(); while (start_pos < end_pos) { - _port_pair_pool.emplace_back(start_pos++); + // 随机端口排序,防止重启后导致分配的端口重复 + _port_pair_pool.insert(it, start_pos++); + it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size())); } } From b4fd445f2d10025a87481e51d6494b00a87f934d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 11:33:54 +0800 Subject: [PATCH 75/92] =?UTF-8?q?webrtc=E6=B5=8B=E8=AF=95=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=B8=8D=E5=86=8D=E5=88=B7=E6=96=B0=E6=B5=81=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- www/webrtc/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 269ae9b2..a340f0c1 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -322,7 +322,7 @@ json.then((json)=> fillStreamList(json)); } setInterval(() => { - get_media_list(); + // get_media_list(); }, 5000); From f3f4b4933264b8513d987b24ea9d8f3e669c87c0 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 11:34:12 +0800 Subject: [PATCH 76/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=82=B9=E6=92=AD=E9=80=9F=E5=BA=A6=E5=90=8E=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=88=B3=E8=B7=B3=E8=B7=83=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/MP4Reader.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Record/MP4Reader.cpp b/src/Record/MP4Reader.cpp index 5c144d2e..2def3781 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -184,8 +184,10 @@ bool MP4Reader::speed(MediaSource &sender, float speed) { WarnL << "播放速度取值范围非法:" << speed; return false; } - //设置播放速度后应该恢复播放 - pause(sender, false); + //_seek_ticker重置,赋值_seek_to + setCurrentStamp(getCurrentStamp()); + // 设置播放速度后应该恢复播放 + _paused = false; if (_speed == speed) { return true; } From cb4ab21548291241aea276462d5ad8d6ea7cdd4c Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 11:34:22 +0800 Subject: [PATCH 77/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drtsp=E7=BB=84=E6=92=AD?= =?UTF-8?q?=E9=81=8D=E5=8E=86=E8=BF=AD=E4=BB=A3=E5=99=A8=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=B4=A9=E6=BA=83=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtsp/UDPServer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Rtsp/UDPServer.cpp b/src/Rtsp/UDPServer.cpp index 4f648ddf..bb9e4642 100644 --- a/src/Rtsp/UDPServer.cpp +++ b/src/Rtsp/UDPServer.cpp @@ -82,10 +82,12 @@ void UDPServer::onRecv(int interleaved, const Buffer::Ptr &buf, struct sockaddr* return; } auto &ref = it0->second; - for (auto it1 = ref.begin(); it1 != ref.end(); ++it1) { + for (auto it1 = ref.begin(); it1 != ref.end();) { auto &func = it1->second; if (!func(interleaved, buf, peer_addr)) { it1 = ref.erase(it1); + } else { + ++it1; } } if (ref.size() == 0) { From f36ccee3de9356a1bbdfc911afbbb68226ffefc3 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 11:34:30 +0800 Subject: [PATCH 78/92] =?UTF-8?q?=E6=9B=B4=E6=96=B0zltoolkit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index bb49f04d..a4b8b5e0 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit bb49f04deeec40eb4331f6e317fd2164af6a7d95 +Subproject commit a4b8b5e00aac6251254a513c7759605c0ba35f90 From 06a6d26491043277361bf5a67dfdadcdca29cb61 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 19:43:11 +0800 Subject: [PATCH 79/92] =?UTF-8?q?=E6=8F=90=E9=AB=98http=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=AE=89=E5=85=A8=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/HttpFileManager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 3ec4d40c..66e5f7fa 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -524,7 +524,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m }); } -static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){ +static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) { @@ -549,6 +549,13 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess } } auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path); + auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path); + if (!start_with(ret, http_root)) { + // 访问的http文件不得在http根目录之外 + throw std::runtime_error("Attempting to access files outside of the http root directory"); + } + // 替换url,防止返回的目录索引网页被注入非法内容 + const_cast(parser).setUrl("/" + ret.substr(http_root.size())); NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast(sender)); return ret; } From 0844f09e24f5fae96da987f16df17dc75e7fe851 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 26 Aug 2023 23:02:19 +0800 Subject: [PATCH 80/92] =?UTF-8?q?addStreamProxy=E9=87=8D=E5=A4=8D=E6=8B=89?= =?UTF-8?q?=E6=B5=81=E6=97=B6=E8=BF=94=E5=9B=9E=E9=94=99=E8=AF=AF=20(#2773?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 11e06ea1..6f40403d 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -537,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream lock_guard lck(s_proxyMapMtx); if (s_proxyMap.find(key) != s_proxyMap.end()) { //已经在拉流了 - cb(SockException(Err_success), key); + cb(SockException(Err_other, "This stream already exists"), key); return; } //添加拉流代理 From 81966f2c4858014a37344555babfe9a1da13b202 Mon Sep 17 00:00:00 2001 From: Dw9 Date: Tue, 29 Aug 2023 11:22:30 +0800 Subject: [PATCH 81/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AA=E5=BC=80?= =?UTF-8?q?=E5=90=AFsctp=E6=97=B6=EF=BC=8Cwebrtc=20datachannel=20sdp?= =?UTF-8?q?=E4=BA=A4=E6=8D=A2=E7=9B=B8=E5=85=B3bug=20(#2791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要优化如下: 1、通过设置port为0 声明不支持datachannel(而不是inactive)。 2、设置正确的ice用户名和密码,防止ice建联失败。 3、group attribute移除datachannel的mid。 3、datachannel 相关sdp移除rtcp相关描述。 --- webrtc/Sdp.cpp | 13 ++++++++----- webrtc/WebRtcTransport.cpp | 6 ++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index 7b2e06e9..7051e42f 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -1555,7 +1555,10 @@ shared_ptr RtcConfigure::createAnswer(const RtcSession &offer) const //设置音视频端口复用 if (!offer.group.mids.empty()) { for (auto &m : ret->media) { - ret->group.mids.emplace_back(m.mid); + //The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute. + if (m.port) { + ret->group.mids.emplace_back(m.mid); + } } } return ret; @@ -1613,15 +1616,15 @@ RETRY: if (offer_media.type == TrackApplication) { RtcMedia answer_media = offer_media; answer_media.role = mathDtlsRole(offer_media.role); -#ifdef ENABLE_SCTP - answer_media.direction = matchDirection(offer_media.direction, configure.direction); - answer_media.candidate = configure.candidate; answer_media.ice_ufrag = configure.ice_ufrag; answer_media.ice_pwd = configure.ice_pwd; answer_media.fingerprint = configure.fingerprint; answer_media.ice_lite = configure.ice_lite; +#ifdef ENABLE_SCTP + answer_media.candidate = configure.candidate; #else - answer_media.direction = RtpDirection::inactive; + answer_media.port = 0; + WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能"; #endif ret->media.emplace_back(answer_media); return; diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 127a1987..1200c148 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -564,8 +564,10 @@ void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { GET_CONFIG(uint16_t, udp_port, Rtc::kPort); GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); - m.rtcp_addr.port = udp_port ? udp_port : tcp_port; - m.port = m.rtcp_addr.port; + m.port = m.port ? (udp_port ? udp_port : tcp_port) : 0; + if (m.type != TrackApplication) { + m.rtcp_addr.port = m.port; + } sdp.origin.address = m.addr.address; } From fef85da7712495c09a967e4181e8314075cd018c Mon Sep 17 00:00:00 2001 From: waken <33921191+mc373906408@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:53:46 +0800 Subject: [PATCH 82/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B0=83=E7=94=A8close?= =?UTF-8?q?=5Fstream=E5=90=8C=E6=97=B6=E8=B0=83=E7=94=A8getMediaList?= =?UTF-8?q?=E5=BC=95=E5=8F=91=E7=9A=84=E5=B4=A9=E6=BA=83=20(#2800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复现条件:chrome打开webrtc demo网页,网页会每秒调用getMediaList。添加一条rtsp拉流,然后再调用close_stream,会偶现进入RtspPlayer::getPacketLossRate函数,然后_rtcp_context为空拿不到指针 --- src/Rtsp/RtspPlayer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 676d89b1..f6a13fda 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -544,6 +544,9 @@ float RtspPlayer::getPacketLossRate(TrackType type) const { size_t lost = 0, expected = 0; try { auto track_idx = getTrackIndexByTrackType(type); + if (_rtcp_context.empty()) { + return 0; + } auto ctx = _rtcp_context[track_idx]; lost = ctx->getLost(); expected = ctx->getExpectedPackets(); From c35b5e9215f0c323d14f21b0542c8bab40f72c67 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 10:50:25 +0800 Subject: [PATCH 83/92] =?UTF-8?q?rtp=E8=BF=87=E6=BB=A4=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E9=99=90=E5=88=B6version=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Rtsp/Rtsp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index 579a881e..daf09680 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -467,7 +467,7 @@ bool isRtp(const char *buf, size_t size) { return false; } RtpHeader *header = (RtpHeader *)buf; - return ((header->pt < 64) || (header->pt >= 96)); + return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion; } bool isRtcp(const char *buf, size_t size) { From d286ac1d739aa4978dd2a84af859dd59f6507874 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 10:52:07 +0800 Subject: [PATCH 84/92] =?UTF-8?q?NoticeCenter=E6=94=AF=E6=8C=81=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2=EF=BC=8C=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E7=9B=91=E5=90=AC=E8=80=85=E5=8F=82=E6=95=B0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=8D=E4=B8=80=E8=87=B4=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- api/source/mk_common.cpp | 2 +- api/source/mk_util.cpp | 2 +- server/WebApi.cpp | 2 +- src/Common/MediaSource.cpp | 6 +++--- src/Common/MultiMediaSourceMuxer.cpp | 2 +- src/Common/config.cpp | 2 +- src/Http/HttpFileManager.cpp | 6 +++--- src/Http/HttpRequester.cpp | 2 +- src/Http/HttpSession.cpp | 6 +++--- src/Record/HlsMakerImp.cpp | 2 +- src/Record/HlsMediaSource.cpp | 2 +- src/Record/MP4Recorder.cpp | 2 +- src/Rtmp/RtmpSession.cpp | 6 +++--- src/Rtp/RtpProcess.cpp | 6 +++--- src/Rtp/RtpServer.cpp | 4 ++-- src/Rtsp/RtspSession.cpp | 12 ++++++------ src/Shell/ShellSession.cpp | 6 +++--- srt/SrtTransportImp.cpp | 9 +++------ webrtc/WebRtcPlayer.cpp | 2 +- webrtc/WebRtcPusher.cpp | 2 +- webrtc/WebRtcTransport.cpp | 4 ++-- 22 files changed, 43 insertions(+), 46 deletions(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index a4b8b5e0..d7dd07dd 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit a4b8b5e00aac6251254a513c7759605c0ba35f90 +Subproject commit d7dd07ddd968ad8f927da2c655e13956be116cef diff --git a/api/source/mk_common.cpp b/api/source/mk_common.cpp index 1604af61..a45068ce 100644 --- a/api/source/mk_common.cpp +++ b/api/source/mk_common.cpp @@ -159,7 +159,7 @@ API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) { } mINI::Instance()[key] = val; //广播配置文件热加载 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); } API_EXPORT const char * API_CALL mk_get_option(const char *key) diff --git a/api/source/mk_util.cpp b/api/source/mk_util.cpp index 6c775529..5c5d3197 100644 --- a/api/source/mk_util.cpp +++ b/api/source/mk_util.cpp @@ -65,7 +65,7 @@ API_EXPORT mk_ini API_CALL mk_ini_default() { static void emit_ini_file_reload(mk_ini ini) { if (ini == mk_ini_default()) { // 广播配置文件热加载 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); } } diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 6f40403d..013171df 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -666,7 +666,7 @@ void installWebApi() { ++changed; } if (changed > 0) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); ini.dumpFile(g_ini_file); } val["changed"] = changed; diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index c0757bf3..1977b616 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -469,7 +469,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s }); }; //广播未找到流,此时可以立即去拉流,这样还来得及 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast(*session), close_player); + NOTICE_EMIT(BroadcastNotFoundStreamArgs, Broadcast::kBroadcastNotFoundStream, info, *session, close_player); } void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr &session, const function &cb) { @@ -499,7 +499,7 @@ void MediaSource::emitEvent(bool regist){ listener->onRegist(*this, regist); } //触发广播 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this); + NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this); InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl(); } @@ -669,7 +669,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ strong_sender->close(false); } else { // 直播时触发无人观看事件,让开发者自行选择是否关闭 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender); } } else { //这个是mp4点播,我们自动关闭 diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 0446abf6..bbb9b357 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -312,7 +312,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() { WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex; strong_self->_rtp_sender.erase(ssrc); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); + NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); }); } }); diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 87e4e855..b113a968 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -31,7 +31,7 @@ bool loadIniConfig(const char *ini_path) { } try { mINI::Instance().parseFile(ini); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); return true; } catch (std::exception &) { InfoL << "dump ini file to:" << ini; diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 66e5f7fa..5577ffbb 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -250,7 +250,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con //cookie有效期为kHlsCookieSecond invoker(err, "", kHlsCookieSecond); }; - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast(sender)); + bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender); if (!flag) { //未开启鉴权,那么允许播放 auth_invoker(""); @@ -383,7 +383,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo } // 事件未被拦截,则认为是http下载请求 - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); + bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender); if (!flag) { // 此事件无人监听,我们默认都有权限访问 callback("", nullptr); @@ -556,7 +556,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess } // 替换url,防止返回的目录索引网页被注入非法内容 const_cast(parser).setUrl("/" + ret.substr(http_root.size())); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast(sender)); + NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender); return ret; } diff --git a/src/Http/HttpRequester.cpp b/src/Http/HttpRequester.cpp index 3e403df2..8bd09950 100644 --- a/src/Http/HttpRequester.cpp +++ b/src/Http/HttpRequester.cpp @@ -271,7 +271,7 @@ static void sendReport() { } static toolkit::onceToken s_token([]() { - NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) { + NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) { // 第一次汇报在程序启动后5分钟 pool.getPoller()->doDelayTask(5 * 60 * 1000, []() { sendReport(); diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 145a08a2..a9d3047f 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -170,7 +170,7 @@ void HttpSession::onError(const SockException &err) { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this); } return; } @@ -311,7 +311,7 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix } }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 onRes(""); @@ -711,7 +711,7 @@ bool HttpSession::emitHttpEvent(bool doInvoke) { }; ///////////////////广播HTTP事件/////////////////////////// bool consumed = false; // 该事件是否被消费 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, static_cast(*this)); + NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this); if (!consumed && doInvoke) { // 该事件无人消费,所以返回404 invoker(404, KeyValue(), HttpBody::Ptr()); diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index bbc49df5..2ba99f76 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -149,7 +149,7 @@ void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { if (broadcastRecordTs) { _info.time_len = duration_ms / 1000.0f; _info.file_size = File::fileSize(_info.file_path.data()); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); + NOTICE_EMIT(BroadcastRecordTsArgs, Broadcast::kBroadcastRecordTs, _info); } } diff --git a/src/Record/HlsMediaSource.cpp b/src/Record/HlsMediaSource.cpp index f3fe516b..96de592b 100644 --- a/src/Record/HlsMediaSource.cpp +++ b/src/Record/HlsMediaSource.cpp @@ -47,7 +47,7 @@ HlsCookieData::~HlsCookieData() { uint64_t bytes = _bytes.load(); if (bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast(*_sock_info)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info); } catch (std::exception &ex) { WarnL << "Exception occurred: " << ex.what(); } diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index ade1d519..5373b8a1 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -92,7 +92,7 @@ void MP4Recorder::asyncClose() { } TraceL << "Emit mp4 record event: " << full_path; //触发mp4录制切片生成事件 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info); + NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info); }); } diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 8a5d8c79..61f0eee1 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -36,7 +36,7 @@ void RtmpSession::onError(const SockException& err) { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this); } //如果是主动关闭的,那么不延迟注销 @@ -215,7 +215,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { on_res(err, option); }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this); if(!flag){ //该事件无人监听,默认鉴权成功 on_res("", ProtocolOption()); @@ -381,7 +381,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){ }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 doPlayResponse("", [token](bool) {}); diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index 0ca3f704..5e61ccf4 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -67,7 +67,7 @@ RtpProcess::~RtpProcess() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this); } catch (std::exception &ex) { WarnL << "Exception occurred: " << ex.what(); } @@ -266,9 +266,9 @@ void RtpProcess::emitOnPublish() { }; //触发推流鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this); if (!flag) { - //该事件无人监听,默认不鉴权 + // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); } } diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 59e207ea..78aee8e1 100644 --- a/src/Rtp/RtpServer.cpp +++ b/src/Rtp/RtpServer.cpp @@ -102,8 +102,8 @@ public: process->setOnDetach(std::move(strong_self->_on_detach)); } if (!process) { // process 未创建,触发rtp server 超时事件 - NoticeCenter::Instance().emitEvent(Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id, - (int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc); + NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id, + (int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc); } } return 0; diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index a660e6be..8d2cc183 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -80,7 +80,7 @@ void RtspSession::onError(const SockException &err) { //流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, *this); } //如果是主动关闭的,那么不延迟注销 @@ -294,7 +294,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { }; //rtsp推流需要鉴权 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, *this); if (!flag) { //该事件无人监听,默认不鉴权 onRes("", ProtocolOption()); @@ -352,7 +352,7 @@ void RtspSession::emitOnPlay(){ }; //广播通用播放url鉴权事件 - auto flag = _emit_on_play ? false : NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = _emit_on_play ? false : NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { //该事件无人监听,默认不鉴权 onRes(""); @@ -392,7 +392,7 @@ void RtspSession::handleReq_Describe(const Parser &parser) { if(_rtsp_realm.empty()){ //广播是否需要rtsp专属认证事件 - if (!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, static_cast(*this))) { + if (!NOTICE_EMIT(BroadcastOnGetRtspRealmArgs, Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, *this)) { //无人监听此事件,说明无需认证 invoker(""); } @@ -497,7 +497,7 @@ void RtspSession::onAuthBasic(const string &realm, const string &auth_base64) { }; //此时必须提供明文密码 - if (!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth, _media_info, realm, user, true, invoker, static_cast(*this))) { + if (!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, user, true, invoker, *this)) { //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; //但是我们还是忽略认证以便完成播放 @@ -581,7 +581,7 @@ void RtspSession::onAuthDigest(const string &realm,const string &auth_md5){ }; //此时可以提供明文或md5加密的密码 - if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth, _media_info, realm, username, false, invoker, static_cast(*this))){ + if(!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, username, false, invoker, *this)){ //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; //但是我们还是忽略认证以便完成播放 diff --git a/src/Shell/ShellSession.cpp b/src/Shell/ShellSession.cpp index fe0c23fd..85c682ee 100644 --- a/src/Shell/ShellSession.cpp +++ b/src/Shell/ShellSession.cpp @@ -135,9 +135,9 @@ inline void ShellSession::pleaseInputPasswd() { }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastShellLogin,_strUserName,passwd,invoker,static_cast(*this)); - if(!flag){ - //如果无人监听shell登录事件,那么默认shell无法登录 + auto flag = NOTICE_EMIT(BroadcastShellLoginArgs, Broadcast::kBroadcastShellLogin, _strUserName, passwd, invoker, *this); + if (!flag) { + // 如果无人监听shell登录事件,那么默认shell无法登录 onAuth("please listen kBroadcastShellLogin event"); } return true; diff --git a/srt/SrtTransportImp.cpp b/srt/SrtTransportImp.cpp index c3c18c5d..054c1812 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -17,7 +17,7 @@ SrtTransportImp::~SrtTransportImp() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, *this); } catch (std::exception &ex) { WarnL << "Exception occurred: " << ex.what(); } @@ -172,9 +172,7 @@ void SrtTransportImp::emitOnPublish() { }; // 触发推流鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPublish, MediaOriginType::srt_push, _media_info, invoker, - static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::srt_push, _media_info, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); @@ -197,8 +195,7 @@ void SrtTransportImp::emitOnPlay() { }); }; - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { doPlay(); } diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index a766607f..ee95ea61 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -77,7 +77,7 @@ void WebRtcPlayer::onDestory() { if (_reader && getSession()) { WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, static_cast(*getSession())); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession()); } } WebRtcTransportImp::onDestory(); diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 0a62c0f3..92b4ad04 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -129,7 +129,7 @@ void WebRtcPusher::onDestory() { if (getSession()) { WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, static_cast(*getSession())); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession()); } } diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 1200c148..82139a7f 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -1218,7 +1218,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana }; // rtsp推流需要鉴权 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, static_cast(sender)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, sender); if (!flag) { // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); @@ -1252,7 +1252,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana }; // 广播通用播放url鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, info, invoker, static_cast(sender)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, info, invoker, sender); if (!flag) { // 该事件无人监听,默认不鉴权 invoker(""); From 39dd886ec15a1b35a712c2189bd99848d9b8123b Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 12:06:35 +0800 Subject: [PATCH 85/92] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E5=88=97=E8=A1=A8=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- server/WebApi.cpp | 18 ++++++++++-------- src/Common/MediaSource.h | 6 +++--- src/FMP4/FMP4MediaSource.h | 4 ++-- src/Http/HttpSession.cpp | 12 ++++++++++-- src/Record/HlsMediaSource.cpp | 6 ++++++ src/Record/HlsMediaSource.h | 5 +++++ src/Rtmp/FlvMuxer.cpp | 6 +++++- src/Rtmp/RtmpMediaSource.h | 4 ++-- src/Rtmp/RtmpSession.cpp | 6 +++++- src/Rtsp/RtspMediaSource.h | 4 ++-- src/Rtsp/RtspSession.cpp | 6 +++++- src/TS/TSMediaSource.h | 4 ++-- srt/SrtTransportImp.cpp | 6 +++++- webrtc/WebRtcPlayer.cpp | 6 +++++- 15 files changed, 68 insertions(+), 27 deletions(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index d7dd07dd..97f9b9a2 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit d7dd07ddd968ad8f927da2c655e13956be116cef +Subproject commit 97f9b9a2ac58353f72f085830690d27833b8ad88 diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 013171df..8ee36526 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -795,22 +795,24 @@ void installWebApi() { throw ApiRetException("can not find the stream", API::NotFound); } src->getPlayerList( - [=](const std::list> &info_list) mutable { + [=](const std::list &info_list) mutable { val["code"] = API::Success; auto &data = val["data"]; data = Value(arrayValue); for (auto &info : info_list) { - auto obj = static_pointer_cast(info); - data.append(std::move(*obj)); + auto &obj = info.get(); + data.append(std::move(obj)); } invoker(200, headerOut, val.toStyledString()); }, - [](std::shared_ptr &&info) -> std::shared_ptr { + [](toolkit::Any &&info) -> toolkit::Any { auto obj = std::make_shared(); - auto session = static_pointer_cast(info); - fillSockInfo(*obj, session.get()); - (*obj)["typeid"] = toolkit::demangle(typeid(*session).name()); - return obj; + auto &sock = info.get(); + fillSockInfo(*obj, &sock); + (*obj)["typeid"] = toolkit::demangle(typeid(sock).name()); + toolkit::Any ret; + ret.set(obj); + return ret; }); }); diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 1329b196..a8403680 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -347,10 +347,10 @@ public: // 观看者个数,包括(hls/rtsp/rtmp) virtual int totalReaderCount(); // 获取播放器列表 - virtual void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) { + virtual void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) { assert(cb); - cb(std::list>()); + cb(std::list()); } // 获取媒体源类型 diff --git a/src/FMP4/FMP4MediaSource.h b/src/FMP4/FMP4MediaSource.h index 53359e30..13674ebe 100644 --- a/src/FMP4/FMP4MediaSource.h +++ b/src/FMP4/FMP4MediaSource.h @@ -51,8 +51,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index a9d3047f..69407928 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -338,7 +338,11 @@ bool HttpSession::checkLiveStreamFMP4(const function &cb) { weak_ptr weak_self = static_pointer_cast(shared_from_this()); fmp4_src->pause(false); _fmp4_reader = fmp4_src->getRing()->attach(getPoller()); - _fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _fmp4_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _fmp4_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -378,7 +382,11 @@ bool HttpSession::checkLiveStreamTS(const function &cb) { weak_ptr weak_self = static_pointer_cast(shared_from_this()); ts_src->pause(false); _ts_reader = ts_src->getRing()->attach(getPoller()); - _ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _ts_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _ts_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/Record/HlsMediaSource.cpp b/src/Record/HlsMediaSource.cpp index 96de592b..f3adb775 100644 --- a/src/Record/HlsMediaSource.cpp +++ b/src/Record/HlsMediaSource.cpp @@ -33,6 +33,12 @@ void HlsCookieData::addReaderCount() { // HlsMediaSource已经销毁 *added = false; }); + auto info = _sock_info; + _ring_reader->setGetInfoCB([info]() { + Any ret; + ret.set(info); + return ret; + }); } } } diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h index 94cfdf4d..1e0ecd14 100644 --- a/src/Record/HlsMediaSource.h +++ b/src/Record/HlsMediaSource.h @@ -58,6 +58,11 @@ public: void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; } + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + _ring->getInfoList(cb, on_change); + } + private: RingType::Ptr _ring; std::string _index_file; diff --git a/src/Rtmp/FlvMuxer.cpp b/src/Rtmp/FlvMuxer.cpp index bee86ddb..b2d1a6ad 100644 --- a/src/Rtmp/FlvMuxer.cpp +++ b/src/Rtmp/FlvMuxer.cpp @@ -46,7 +46,11 @@ void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr std::weak_ptr weak_self = getSharedPtr(); media->pause(false); _ring_reader = media->getRing()->attach(poller); - _ring_reader->setGetInfoCB([weak_self]() { return dynamic_pointer_cast(weak_self.lock()); }); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(dynamic_pointer_cast(weak_self.lock())); + return ret; + }); _ring_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 718f6055..732cbb85 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -57,8 +57,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 61f0eee1..fa3058be 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -306,7 +306,11 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr src->pause(false); _ring_reader = src->getRing()->attach(getPoller()); weak_ptr weak_self = static_pointer_cast(shared_from_this()); - _ring_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index f70396d8..d1faa1d4 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -53,8 +53,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 8d2cc183..d780a66d 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -857,7 +857,11 @@ void RtspSession::handleReq_Play(const Parser &parser) { if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) { weak_ptr weak_self = static_pointer_cast(shared_from_this()); _play_reader = play_src->getRing()->attach(getPoller(), use_gop); - _play_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _play_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _play_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/TS/TSMediaSource.h b/src/TS/TSMediaSource.h index 71797c1b..b691d15f 100644 --- a/src/TS/TSMediaSource.h +++ b/src/TS/TSMediaSource.h @@ -50,8 +50,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/srt/SrtTransportImp.cpp b/srt/SrtTransportImp.cpp index 054c1812..56d6bd8a 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -224,7 +224,11 @@ void SrtTransportImp::doPlay() { ts_src->pause(false); strong_self->_ts_reader = ts_src->getRing()->attach(strong_self->getPoller()); weak_ptr weak_session = strong_self->getSession(); - strong_self->_ts_reader->setGetInfoCB([weak_session]() { return weak_session.lock(); }); + strong_self->_ts_reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); strong_self->_ts_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index ee95ea61..4d8d9b9f 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -48,7 +48,11 @@ void WebRtcPlayer::onStartWebRTC() { _reader = playSrc->getRing()->attach(getPoller(), true); weak_ptr weak_self = static_pointer_cast(shared_from_this()); weak_ptr weak_session = static_pointer_cast(getSession()); - _reader->setGetInfoCB([weak_session]() { return weak_session.lock(); }); + _reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { From e3e7495c9060e00c5bb98a91d7ac3a96d44dff19 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 12:53:56 +0800 Subject: [PATCH 86/92] =?UTF-8?q?=E6=96=B0=E5=A2=9Ewebrtc=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8datachannel=E6=B6=88=E6=81=AF=E5=B9=BF?= =?UTF-8?q?=E6=92=AD=E6=9C=BA=E5=88=B6=E5=92=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- server/WebApi.cpp | 13 +++++++++++++ src/Common/MediaSource.h | 2 ++ src/Rtsp/RtspMediaSource.h | 7 +++++++ webrtc/WebRtcPlayer.cpp | 15 +++++++++++++++ webrtc/WebRtcTransport.cpp | 13 +++++++++++++ webrtc/WebRtcTransport.h | 1 + 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 97f9b9a2..5d74e09b 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 97f9b9a2ac58353f72f085830690d27833b8ad88 +Subproject commit 5d74e09b8c84cccc46036ed2ef1a62f670c423d4 diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 8ee36526..e8081756 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -816,6 +816,19 @@ void installWebApi() { }); }); + api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) { + CHECK_SECRET(); + CHECK_ARGS("schema", "vhost", "app", "stream", "msg"); + auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]); + if (!src) { + throw ApiRetException("can not find the stream", API::NotFound); + } + Any any; + Buffer::Ptr buffer = std::make_shared(allArgs["msg"]); + any.set(std::move(buffer)); + src->broadcastMessage(any); + }); + //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){ CHECK_SECRET(); diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index a8403680..bcf75a09 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -353,6 +353,8 @@ public: cb(std::list()); } + virtual bool broadcastMessage(const toolkit::Any &data) { return false; } + // 获取媒体源类型 MediaOriginType getOriginType() const; // 获取媒体源url或者文件路径 diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index d1faa1d4..bac7fcf9 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -55,9 +55,16 @@ public: void getPlayerList(const std::function &info_list)> &cb, const std::function &on_change) override { + assert(_ring); _ring->getInfoList(cb, on_change); } + bool broadcastMessage(const toolkit::Any &data) override { + assert(_ring); + _ring->sendMessage(data); + return true; + } + /** * 获取播放器个数 */ diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index 4d8d9b9f..63f174eb 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -71,6 +71,21 @@ void WebRtcPlayer::onStartWebRTC() { } strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); }); + + _reader->setMessageCB([weak_self] (const toolkit::Any &data) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (data.is()) { + auto &buffer = data.get(); + // PPID 51: 文本string + // PPID 53: 二进制 + strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); + } else { + WarnL << "Send unknown message type to webrtc player: " << data.type_name(); + } + }); } } void WebRtcPlayer::onDestory() { diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 82139a7f..4ec13e6f 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -229,6 +229,19 @@ void WebRtcTransport::OnSctpAssociationMessageReceived( _sctp->SendSctpMessage(params, ppid, msg, len); } #endif + +void WebRtcTransport::sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { +#ifdef ENABLE_SCTP + if (_sctp) { + RTC::SctpStreamParameters params; + params.streamId = streamId; + _sctp->SendSctpMessage(params, ppid, (uint8_t *)msg, len); + } +#else + WarnL << "WebRTC datachannel disabled!"; +#endif +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WebRtcTransport::sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple) { diff --git a/webrtc/WebRtcTransport.h b/webrtc/WebRtcTransport.h index 2534ca3b..2d1e6bbf 100644 --- a/webrtc/WebRtcTransport.h +++ b/webrtc/WebRtcTransport.h @@ -112,6 +112,7 @@ public: */ void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); + void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len); const EventPoller::Ptr& getPoller() const; Session::Ptr getSession() const; From 144165d53733ed1afb5f08e440e22bead3832972 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 12:58:20 +0800 Subject: [PATCH 87/92] =?UTF-8?q?=E6=96=B0=E5=A2=9Emk=5Fmedia=5Fsource=5Fb?= =?UTF-8?q?roadcast=5Fmsg=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过该接口可以广播webrtc datachannel消息给webrtc播放器 --- api/include/mk_events_objects.h | 3 +++ api/source/mk_events_objects.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/api/include/mk_events_objects.h b/api/include/mk_events_objects.h index 018988f4..3dc9a2c3 100644 --- a/api/include/mk_events_objects.h +++ b/api/include/mk_events_objects.h @@ -100,6 +100,9 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx); // copy track reference by index from MediaSource, please use mk_track_unref to release it API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index); +// MediaSource::broadcastMessage +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len); + /** * 直播源在ZLMediaKit中被称作为MediaSource, * 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index fd7f696c..f6b535bf 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -214,6 +214,16 @@ API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx return (mk_track) new Track::Ptr(std::move(tracks[index])); } +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) { + assert(ctx && msg && len); + MediaSource *src = (MediaSource *)ctx; + + Any any; + Buffer::Ptr buffer = std::make_shared(std::string(msg, len)); + any.set(std::move(buffer)); + src->broadcastMessage(any); +} + API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){ assert(ctx); MediaSource *src = (MediaSource *)ctx; From 11fdffe84aa6879da6694fa1fcb978ac37510724 Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 13:00:36 +0800 Subject: [PATCH 88/92] =?UTF-8?q?=E6=96=B0=E5=A2=9EbroadcastMessage?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- postman/ZLMediaKit.postman_collection.json | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index bf0ace5f..fdda9d2a 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -912,6 +912,56 @@ }, "response": [] }, + { + "name": "广播webrtc datachannel消息(broadcastMessage)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "broadcastMessage" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置)" + }, + { + "key": "schema", + "value": "rtsp", + "description": "协议,例如 rtsp或rtmp,目前仅支持rtsp协议" + }, + { + "key": "vhost", + "value": "{{defaultVhost}}", + "description": "虚拟主机,例如__defaultVhost__" + }, + { + "key": "app", + "value": "live", + "description": "应用名,例如 live" + }, + { + "key": "stream", + "value": "test", + "description": "流id,例如 test" + }, + { + "key": "msg", + "value": "Hello ZLMediakit" + } + ] + } + }, + "response": [] + }, { "name": "获取流信息(getMediaInfo)", "request": { From c83a3c563954746a0927c371ff7865e4c64bb97d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sat, 2 Sep 2023 13:06:42 +0800 Subject: [PATCH 89/92] =?UTF-8?q?mk=5Fmedia=5Fsource=5Fbroadcast=5Fmsg?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E8=BF=94=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/source/mk_events_objects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index f6b535bf..3833017f 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -221,7 +221,7 @@ API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, Any any; Buffer::Ptr buffer = std::make_shared(std::string(msg, len)); any.set(std::move(buffer)); - src->broadcastMessage(any); + return src->broadcastMessage(any); } API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){ From 6fa4d1b92fbb944f3e6e97d6ddb5152143dec0b3 Mon Sep 17 00:00:00 2001 From: Deepslient <1154547394@qq.com> Date: Sat, 2 Sep 2023 11:24:01 +0800 Subject: [PATCH 90/92] Add semicolons to unify JS format --- www/webrtc/index.html | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/www/webrtc/index.html b/www/webrtc/index.html index a340f0c1..a29629bd 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -89,12 +89,12 @@