From cb0579a16d2ca020476047f8351ce01a313ee7f8 Mon Sep 17 00:00:00 2001 From: XiaoYan Lin Date: Sun, 2 Jul 2023 12:02:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81hls-fmp4?= =?UTF-8?q?=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()); }