Commit cb0579a1 by XiaoYan Lin Committed by GitHub

新增支持hls-fmp4直播(#2603 #977 #1965)

同时主要优化点包括:
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 <linxiaoyan87@foxmail.com>
parent 6aa4b741
...@@ -47,19 +47,14 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") ...@@ -47,19 +47,14 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
# TODO: 补一个函数处理各种库 # TODO: 补一个函数处理各种库
# 添加 mov、flv 库用于 MP4 录制 # 添加 mov、flv 库用于 MP4 录制
if(ENABLE_MP4) if (ENABLE_MP4 OR ENABLE_HLS_FMP4)
message(STATUS "ENABLE_MP4 defined")
# MOV # MOV
set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov)
aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) 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(mov STATIC ${MOV_SRC_LIST})
add_library(MediaServer::mov ALIAS mov) add_library(MediaServer::mov ALIAS mov)
target_compile_definitions(mov target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT})
PUBLIC -DENABLE_MP4)
target_compile_options(mov
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_include_directories(mov target_include_directories(mov
PRIVATE PRIVATE
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>" "$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
...@@ -72,19 +67,24 @@ if(ENABLE_MP4) ...@@ -72,19 +67,24 @@ if(ENABLE_MP4)
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(flv STATIC ${FLV_SRC_LIST})
add_library(MediaServer::flv ALIAS flv) add_library(MediaServer::flv ALIAS flv)
target_compile_options(flv target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT})
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
target_include_directories(flv target_include_directories(flv
PRIVATE PRIVATE
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>" "$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
PUBLIC PUBLIC
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>") "$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
update_cached_list(MK_LINK_LIBRARIES update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov)
MediaServer::flv MediaServer::mov)
update_cached_list(MK_COMPILE_DEFINITIONS if (ENABLE_MP4)
ENABLE_MP4) message(STATUS "ENABLE_MP4 defined")
endif() 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 生成 # 添加 mpeg 用于支持 ts 生成
if(ENABLE_RTPPROXY OR ENABLE_HLS) if(ENABLE_RTPPROXY OR ENABLE_HLS)
...@@ -108,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) ...@@ -108,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS)
update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg)
if(ENABLE_RTPPROXY) if(ENABLE_RTPPROXY)
message(STATUS "ENABLE_RTPPROXY defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY)
endif() endif()
if(ENABLE_HLS) if(ENABLE_HLS)
message(STATUS "ENABLE_HLS defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS)
endif() endif()
endif() endif()
......
...@@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON) ...@@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON)
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF) option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF) option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF)
option(ENABLE_MP4 "Enable MP4" ON) 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_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
option(ENABLE_MYSQL "Enable MySQL" OFF) option(ENABLE_MYSQL "Enable MySQL" OFF)
option(ENABLE_OPENSSL "Enable OpenSSL" ON) option(ENABLE_OPENSSL "Enable OpenSSL" ON)
......
...@@ -45,8 +45,10 @@ add_mute_audio=1 ...@@ -45,8 +45,10 @@ add_mute_audio=1
#此参数不应大于播放器超时时间;单位毫秒 #此参数不应大于播放器超时时间;单位毫秒
continue_push_ms=15000 continue_push_ms=15000
#是否开启转换为hls #是否开启转换为hls(mpegts)
enable_hls=1 enable_hls=1
#是否开启转换为hls(fmp4)
enable_hls_fmp4=0
#是否开启MP4录制 #是否开启MP4录制
enable_mp4=0 enable_mp4=0
#是否开启转换为rtsp/webrtc #是否开启转换为rtsp/webrtc
...@@ -124,7 +126,7 @@ segDur=2 ...@@ -124,7 +126,7 @@ segDur=2
segNum=3 segNum=3
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
segRetain=5 segRetain=5
#是否广播 ts 切片完成通知 #是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
broadcastRecordTs=0 broadcastRecordTs=0
#直播hls文件删除延时,单位秒,issue: #913 #直播hls文件删除延时,单位秒,issue: #913
deleteDelaySec=10 deleteDelaySec=10
...@@ -150,7 +152,7 @@ on_play=https://127.0.0.1/index/hook/on_play ...@@ -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 on_publish=https://127.0.0.1/index/hook/on_publish
#录制mp4切片完成事件 #录制mp4切片完成事件
on_record_mp4=https://127.0.0.1/index/hook/on_record_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 on_record_ts=https://127.0.0.1/index/hook/on_record_ts
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
......
...@@ -59,6 +59,7 @@ ProtocolOption::ProtocolOption() { ...@@ -59,6 +59,7 @@ ProtocolOption::ProtocolOption() {
GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); 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_mp4, Protocol::kEnableMP4);
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
...@@ -83,6 +84,7 @@ ProtocolOption::ProtocolOption() { ...@@ -83,6 +84,7 @@ ProtocolOption::ProtocolOption() {
continue_push_ms = s_continue_push_ms; continue_push_ms = s_continue_push_ms;
enable_hls = s_enable_hls; enable_hls = s_enable_hls;
enable_hls_fmp4 = s_enable_hls_fmp4;
enable_mp4 = s_enable_mp4; enable_mp4 = s_enable_mp4;
enable_rtsp = s_enable_rtsp; enable_rtsp = s_enable_rtsp;
enable_rtmp = s_enable_rtmp; enable_rtmp = s_enable_rtmp;
......
...@@ -151,8 +151,10 @@ public: ...@@ -151,8 +151,10 @@ public:
//断连续推延时,单位毫秒,默认采用配置文件 //断连续推延时,单位毫秒,默认采用配置文件
uint32_t continue_push_ms; uint32_t continue_push_ms;
//是否开启转换为hls //是否开启转换为hls(mpegts)
bool enable_hls; bool enable_hls;
//是否开启转换为hls(fmp4)
bool enable_hls_fmp4;
//是否开启MP4录制 //是否开启MP4录制
bool enable_mp4; bool enable_mp4;
//是否开启转换为rtsp/webrtc //是否开启转换为rtsp/webrtc
...@@ -194,6 +196,7 @@ public: ...@@ -194,6 +196,7 @@ public:
GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(continue_push_ms);
GET_OPT_VALUE(enable_hls); GET_OPT_VALUE(enable_hls);
GET_OPT_VALUE(enable_hls_fmp4);
GET_OPT_VALUE(enable_mp4); GET_OPT_VALUE(enable_mp4);
GET_OPT_VALUE(enable_rtsp); GET_OPT_VALUE(enable_rtsp);
GET_OPT_VALUE(enable_rtmp); GET_OPT_VALUE(enable_rtmp);
......
...@@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, con ...@@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, con
for (auto &track : tracks) { for (auto &track : tracks) {
recorder->addTrack(track); recorder->addTrack(track);
} }
recorder->addTrackCompleted();
return recorder; return recorder;
} }
...@@ -97,17 +98,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_ ...@@ -97,17 +98,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_
if (option.enable_hls) { if (option.enable_hls) {
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option)); _hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
} }
if (option.enable_hls_fmp4) {
_hls_fmp4 = dynamic_pointer_cast<HlsFMP4Recorder>(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option));
}
if (option.enable_mp4) { if (option.enable_mp4) {
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option); _mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
} }
if (option.enable_ts) { if (option.enable_ts) {
_ts = std::make_shared<TSMediaSourceMuxer>(_tuple, option); _ts = dynamic_pointer_cast<TSMediaSourceMuxer>(Recorder::createRecorder(Recorder::type_ts, _tuple, option));
} }
#if defined(ENABLE_MP4)
if (option.enable_fmp4) { if (option.enable_fmp4) {
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option); _fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
} }
#endif
//音频相关设置 //音频相关设置
enableAudio(option.enable_audio); enableAudio(option.enable_audio);
...@@ -128,14 +130,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven ...@@ -128,14 +130,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
if (_ts) { if (_ts) {
_ts->setListener(self); _ts->setListener(self);
} }
#if defined(ENABLE_MP4)
if (_fmp4) { if (_fmp4) {
_fmp4->setListener(self); _fmp4->setListener(self);
} }
#endif if (_hls_fmp4) {
auto hls = _hls; _hls_fmp4->setListener(self);
if (hls) { }
hls->setListener(self); if (_hls) {
_hls->setListener(self);
} }
} }
...@@ -144,15 +146,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list ...@@ -144,15 +146,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list
} }
int MultiMediaSourceMuxer::totalReaderCount() const { int MultiMediaSourceMuxer::totalReaderCount() const {
auto hls = _hls;
return (_rtsp ? _rtsp->readerCount() : 0) + return (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) +
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->readerCount() : 0) + (_fmp4 ? _fmp4->readerCount() : 0) +
#endif
(_mp4 ? _option.mp4_as_player : 0) + (_mp4 ? _option.mp4_as_player : 0) +
(hls ? hls->readerCount() : 0) + (_hls ? _hls->readerCount() : 0) +
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
(_ring ? _ring->readerCount() : 0); (_ring ? _ring->readerCount() : 0);
} }
...@@ -180,6 +180,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { ...@@ -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) { 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, [&]() { onceToken token(nullptr, [&]() {
if (_option.mp4_as_player && type == Recorder::type_mp4) { if (_option.mp4_as_player && type == Recorder::type_mp4) {
//开启关闭mp4录制,触发观看人数变化相关事件 //开启关闭mp4录制,触发观看人数变化相关事件
...@@ -215,19 +216,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type ...@@ -215,19 +216,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
} }
return true; return true;
} }
case Recorder::type_hls_fmp4: {
if (start && !_hls_fmp4) {
//开始录制
_option.hls_save_path = custom_path;
auto hls = dynamic_pointer_cast<HlsFMP4Recorder>(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<FMP4MediaSourceMuxer>(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<TSMediaSourceMuxer>(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; default : return false;
} }
} }
//此函数可能跨线程调用 //此函数可能跨线程调用
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) { bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
switch (type){ switch (type) {
case Recorder::type_hls : case Recorder::type_hls: return !!_hls;
return !!_hls; case Recorder::type_mp4: return !!_mp4;
case Recorder::type_mp4 : case Recorder::type_hls_fmp4: return !!_hls_fmp4;
return !!_mp4; case Recorder::type_fmp4: return !!_fmp4;
default: case Recorder::type_ts: return !!_ts;
return false; default: return false;
} }
} }
...@@ -327,20 +368,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { ...@@ -327,20 +368,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
if (_ts) { if (_ts) {
ret = _ts->addTrack(track) ? true : ret; ret = _ts->addTrack(track) ? true : ret;
} }
#if defined(ENABLE_MP4)
if (_fmp4) { if (_fmp4) {
ret = _fmp4->addTrack(track) ? true : ret; ret = _fmp4->addTrack(track) ? true : ret;
} }
#endif if (_hls) {
ret = _hls->addTrack(track) ? true : ret;
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 }
auto hls = _hls; if (_hls_fmp4) {
if (hls) { ret = _hls_fmp4->addTrack(track) ? true : ret;
ret = hls->addTrack(track) ? true : ret;
} }
auto mp4 = _mp4; if (_mp4) {
if (mp4) { ret = _mp4->addTrack(track) ? true : ret;
ret = mp4->addTrack(track) ? true : ret;
} }
return ret; return ret;
} }
...@@ -350,16 +388,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() { ...@@ -350,16 +388,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
setMediaListener(getDelegate()); setMediaListener(getDelegate());
if (_rtmp) { if (_rtmp) {
_rtmp->onAllTrackReady(); _rtmp->addTrackCompleted();
} }
if (_rtsp) { if (_rtsp) {
_rtsp->onAllTrackReady(); _rtsp->addTrackCompleted();
}
if (_ts) {
_ts->addTrackCompleted();
}
if (_mp4) {
_mp4->addTrackCompleted();
} }
#if defined(ENABLE_MP4)
if (_fmp4) { if (_fmp4) {
_fmp4->onAllTrackReady(); _fmp4->addTrackCompleted();
} }
#endif if (_hls) {
_hls->addTrackCompleted();
}
if (_hls_fmp4) {
_hls_fmp4->addTrackCompleted();
}
auto listener = _track_listener.lock(); auto listener = _track_listener.lock();
if (listener) { if (listener) {
listener->onAllTrackReady(); listener->onAllTrackReady();
...@@ -407,21 +456,17 @@ void MultiMediaSourceMuxer::resetTracks() { ...@@ -407,21 +456,17 @@ void MultiMediaSourceMuxer::resetTracks() {
if (_ts) { if (_ts) {
_ts->resetTracks(); _ts->resetTracks();
} }
#if defined(ENABLE_MP4)
if (_fmp4) { if (_fmp4) {
_fmp4->resetTracks(); _fmp4->resetTracks();
} }
#endif if (_hls_fmp4) {
_hls_fmp4->resetTracks();
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls;
if (hls) {
hls->resetTracks();
} }
if (_hls) {
auto mp4 = _mp4; _hls->resetTracks();
if (mp4) { }
mp4->resetTracks(); if (_mp4) {
_mp4->resetTracks();
} }
} }
...@@ -443,23 +488,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { ...@@ -443,23 +488,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
ret = _ts->inputFrame(frame) ? true : ret; ret = _ts->inputFrame(frame) ? true : ret;
} }
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 if (_hls) {
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 ret = _hls->inputFrame(frame) ? true : ret;
auto hls = _hls;
if (hls) {
ret = hls->inputFrame(frame) ? true : ret;
} }
auto mp4 = _mp4;
if (mp4) { if (_hls_fmp4) {
ret = mp4->inputFrame(frame) ? true : ret; ret = _hls_fmp4->inputFrame(frame) ? true : ret;
} }
#if defined(ENABLE_MP4) if (_mp4) {
ret = _mp4->inputFrame(frame) ? true : ret;
}
if (_fmp4) { if (_fmp4) {
ret = _fmp4->inputFrame(frame) ? true : ret; ret = _fmp4->inputFrame(frame) ? true : ret;
} }
#endif
if (_ring) { if (_ring) {
if (frame->getTrackType() == TrackVideo) { if (frame->getTrackType() == TrackVideo) {
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处 // 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
...@@ -481,15 +523,14 @@ bool MultiMediaSourceMuxer::isEnabled(){ ...@@ -481,15 +523,14 @@ bool MultiMediaSourceMuxer::isEnabled(){
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) { if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
//无人观看时,每次检查是否真的无人观看 //无人观看时,每次检查是否真的无人观看
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能) //有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
auto hls = _hls;
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) || _is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
(_rtsp ? _rtsp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) ||
(_ts ? _ts->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) ||
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->isEnabled() : false) || (_fmp4 ? _fmp4->isEnabled() : false) ||
#endif
(_ring ? (bool)_ring->readerCount() : false) || (_ring ? (bool)_ring->readerCount() : false) ||
(hls ? hls->isEnabled() : false) || _mp4; (_hls ? _hls->isEnabled() : false) ||
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
_mp4;
if (_is_enable) { if (_is_enable) {
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu //无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu
......
...@@ -164,18 +164,14 @@ private: ...@@ -164,18 +164,14 @@ private:
toolkit::Ticker _last_check; toolkit::Ticker _last_check;
Stamp _stamp[2]; Stamp _stamp[2];
std::weak_ptr<Listener> _track_listener; std::weak_ptr<Listener> _track_listener;
#if defined(ENABLE_RTPPROXY)
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender; std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
#endif //ENABLE_RTPPROXY
#if defined(ENABLE_MP4)
FMP4MediaSourceMuxer::Ptr _fmp4; FMP4MediaSourceMuxer::Ptr _fmp4;
#endif
RtmpMediaSourceMuxer::Ptr _rtmp; RtmpMediaSourceMuxer::Ptr _rtmp;
RtspMediaSourceMuxer::Ptr _rtsp; RtspMediaSourceMuxer::Ptr _rtsp;
TSMediaSourceMuxer::Ptr _ts; TSMediaSourceMuxer::Ptr _ts;
MediaSinkInterface::Ptr _mp4; MediaSinkInterface::Ptr _mp4;
HlsRecorder::Ptr _hls; HlsRecorder::Ptr _hls;
HlsFMP4Recorder::Ptr _hls_fmp4;
toolkit::EventPoller::Ptr _poller; toolkit::EventPoller::Ptr _poller;
RingType::Ptr _ring; RingType::Ptr _ring;
......
...@@ -102,6 +102,7 @@ const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; ...@@ -102,6 +102,7 @@ const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio";
const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
const string kEnableHls = PROTOCOL_FIELD "enable_hls"; const string kEnableHls = PROTOCOL_FIELD "enable_hls";
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
...@@ -127,6 +128,7 @@ static onceToken token([]() { ...@@ -127,6 +128,7 @@ static onceToken token([]() {
mINI::Instance()[kContinuePushMS] = 15000; mINI::Instance()[kContinuePushMS] = 15000;
mINI::Instance()[kEnableHls] = 1; mINI::Instance()[kEnableHls] = 1;
mINI::Instance()[kEnableHlsFmp4] = 0;
mINI::Instance()[kEnableMP4] = 0; mINI::Instance()[kEnableMP4] = 0;
mINI::Instance()[kEnableRtsp] = 1; mINI::Instance()[kEnableRtsp] = 1;
mINI::Instance()[kEnableRtmp] = 1; mINI::Instance()[kEnableRtmp] = 1;
......
...@@ -193,8 +193,10 @@ extern const std::string kAddMuteAudio; ...@@ -193,8 +193,10 @@ extern const std::string kAddMuteAudio;
//断连续推延时,单位毫秒,默认采用配置文件 //断连续推延时,单位毫秒,默认采用配置文件
extern const std::string kContinuePushMS; extern const std::string kContinuePushMS;
//是否开启转换为hls //是否开启转换为hls(mpegts)
extern const std::string kEnableHls; extern const std::string kEnableHls;
//是否开启转换为hls(fmp4)
extern const std::string kEnableHlsFmp4;
//是否开启MP4录制 //是否开启MP4录制
extern const std::string kEnableMP4; extern const std::string kEnableMP4;
//是否开启转换为rtsp/webrtc //是否开启转换为rtsp/webrtc
......
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
#define HLS_SCHEMA "hls" #define HLS_SCHEMA "hls"
#define TS_SCHEMA "ts" #define TS_SCHEMA "ts"
#define FMP4_SCHEMA "fmp4" #define FMP4_SCHEMA "fmp4"
#define HLS_FMP4_SCHEMA "hls.fmp4"
#define SRT_SCHEMA "srt" #define SRT_SCHEMA "srt"
#define DEFAULT_VHOST "__defaultVhost__" #define DEFAULT_VHOST "__defaultVhost__"
......
...@@ -11,8 +11,6 @@ ...@@ -11,8 +11,6 @@
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H #ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H #define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
#if defined(ENABLE_MP4)
#include "FMP4MediaSource.h" #include "FMP4MediaSource.h"
#include "Record/MP4Muxer.h" #include "Record/MP4Muxer.h"
...@@ -63,7 +61,8 @@ public: ...@@ -63,7 +61,8 @@ public:
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true; return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
} }
void onAllTrackReady() { void addTrackCompleted() override {
MP4MuxerMemory::addTrackCompleted();
_media_src->setInitSegment(getInitSegment()); _media_src->setInitSegment(getInitSegment());
} }
...@@ -86,5 +85,4 @@ private: ...@@ -86,5 +85,4 @@ private:
}//namespace mediakit }//namespace mediakit
#endif// defined(ENABLE_MP4)
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H #endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
...@@ -33,6 +33,7 @@ namespace mediakit { ...@@ -33,6 +33,7 @@ namespace mediakit {
static int kHlsCookieSecond = 60; static int kHlsCookieSecond = 60;
static const string kCookieName = "ZL_COOKIE"; static const string kCookieName = "ZL_COOKIE";
static const string kHlsSuffix = "/hls.m3u8"; static const string kHlsSuffix = "/hls.m3u8";
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
struct HttpCookieAttachment { struct HttpCookieAttachment {
//是否已经查找到过MediaSource //是否已经查找到过MediaSource
...@@ -278,7 +279,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo ...@@ -278,7 +279,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
HttpCookieManager::Instance().delCookie(cookie); 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<SockInfoImp>(); SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
info->_identifier = sender.getIdentifier(); info->_identifier = sender.getIdentifier();
...@@ -355,7 +356,7 @@ static string pathCat(const string &a, const string &b){ ...@@ -355,7 +356,7 @@ static string pathCat(const string &a, const string &b){
* @param cb 回调对象 * @param cb 回调对象
*/ */
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &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())) { if (!is_hls && !File::fileExist(file_path.data())) {
//文件不存在且不是hls,那么直接返回404 //文件不存在且不是hls,那么直接返回404
sendNotFound(cb); sendNotFound(cb);
...@@ -363,8 +364,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m ...@@ -363,8 +364,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
} }
if (is_hls) { if (is_hls) {
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS // hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
if (end_with(file_path, kHlsSuffix)) {
const_cast<string &>(media_info.schema) = HLS_SCHEMA; const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, ""); replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
} else {
const_cast<string &>(media_info.schema) = HLS_FMP4_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsFMP4Suffix, "");
}
} }
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this()); weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
* may be found in the AUTHORS file in the root of the source tree. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include <iomanip>
#include "HlsMaker.h" #include "HlsMaker.h"
#include "Common/config.h" #include "Common/config.h"
...@@ -15,83 +16,76 @@ using namespace std; ...@@ -15,83 +16,76 @@ using namespace std;
namespace mediakit { 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个切片代表点播 //最小允许设置为0,0个切片代表点播
_seg_number = seg_number; _seg_number = seg_number;
_seg_duration = seg_duration; _seg_duration = seg_duration;
_seg_keep = seg_keep; _seg_keep = seg_keep;
} }
HlsMaker::~HlsMaker() = default;
void HlsMaker::makeIndexFile(bool eof) { void HlsMaker::makeIndexFile(bool eof) {
char file_content[1024];
int maxSegmentDuration = 0; int maxSegmentDuration = 0;
for (auto &tp : _seg_dur_list) { for (auto &tp : _seg_dur_list) {
int dur = std::get<0>(tp); int dur = std::get<0>(tp);
if (dur > maxSegmentDuration) { if (dur > maxSegmentDuration) {
maxSegmentDuration = dur; 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 index_str;
index_str.reserve(2048);
string m3u8; index_str += "#EXTM3U\n";
index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n");
if (_seg_number == 0) { if (_seg_number == 0) {
// 录像点播支持时移 index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
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);
} else { } else {
snprintf(file_content, sizeof(file_content), index_str += "#EXT-X-ALLOW-CACHE:NO\n";
"#EXTM3U\n" }
"#EXT-X-VERSION:3\n" index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n";
"#EXT-X-ALLOW-CACHE:NO\n" index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n";
"#EXT-X-TARGETDURATION:%u\n" if (_is_fmp4) {
"#EXT-X-MEDIA-SEQUENCE:%llu\n", index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n";
(maxSegmentDuration + 999) / 1000,
sequence);
} }
m3u8.assign(file_content); stringstream ss;
for (auto &tp : _seg_dur_list) { 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()); ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n";
m3u8.append(file_content);
} }
index_str += ss.str();
if (eof) { if (eof) {
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); index_str += "#EXT-X-ENDLIST\n";
m3u8.append(file_content);
} }
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 (data && len) {
if (timestamp < _last_timestamp) { if (timestamp < _last_timestamp) {
//时间戳回退了,切片时长重新计时 // 时间戳回退了,切片时长重新计时
WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
_last_seg_timestamp = _last_timestamp = timestamp; _last_seg_timestamp = _last_timestamp = timestamp;
} }
if (is_idr_fast_packet) { if (is_idr_fast_packet) {
//尝试切片ts // 尝试切片ts
addNewSegment(timestamp); addNewSegment(timestamp);
} }
if (!_last_file_name.empty()) { if (!_last_file_name.empty()) {
//存在切片才写入ts数据 // 存在切片才写入ts数据
onWriteSegment((char *) data, len); onWriteSegment(data, len);
_last_timestamp = timestamp; _last_timestamp = timestamp;
} }
} else { } else {
//resetTracks时触发此逻辑 // resetTracks时触发此逻辑
flushLastSegment(false); flushLastSegment(false);
} }
} }
...@@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){ ...@@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){
makeIndexFile(eof); makeIndexFile(eof);
} }
bool HlsMaker::isLive() { bool HlsMaker::isLive() const {
return _seg_number != 0; return _seg_number != 0;
} }
bool HlsMaker::isKeep() { bool HlsMaker::isKeep() const {
return _seg_keep; return _seg_keep;
} }
bool HlsMaker::isFmp4() const {
return _is_fmp4;
}
void HlsMaker::clear() { void HlsMaker::clear() {
_file_index = 0; _file_index = 0;
_last_timestamp = 0; _last_timestamp = 0;
......
...@@ -21,12 +21,13 @@ namespace mediakit { ...@@ -21,12 +21,13 @@ namespace mediakit {
class HlsMaker { class HlsMaker {
public: public:
/** /**
* @param is_fmp4 使用fmp4还是mpegts
* @param seg_duration 切片文件长度 * @param seg_duration 切片文件长度
* @param seg_number 切片个数 * @param seg_number 切片个数
* @param seg_keep 是否保留切片文件 * @param seg_keep 是否保留切片文件
*/ */
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
virtual ~HlsMaker(); virtual ~HlsMaker() = default;
/** /**
* 写入ts数据 * 写入ts数据
...@@ -35,17 +36,29 @@ public: ...@@ -35,17 +36,29 @@ public:
* @param timestamp 毫秒时间戳 * @param timestamp 毫秒时间戳
* @param is_idr_fast_packet 是否为关键帧第一个包 * @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;
/** /**
* 清空记录 * 清空记录
...@@ -67,6 +80,13 @@ protected: ...@@ -67,6 +80,13 @@ protected:
virtual void onDelSegment(uint64_t index) = 0; virtual void onDelSegment(uint64_t index) = 0;
/** /**
* 写init.mp4切片文件回调
* @param data
* @param len
*/
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
/**
* 写ts切片文件回调 * 写ts切片文件回调
* @param data * @param data
* @param len * @param len
...@@ -109,6 +129,7 @@ private: ...@@ -109,6 +129,7 @@ private:
void addNewSegment(uint64_t timestamp); void addNewSegment(uint64_t timestamp);
private: private:
bool _is_fmp4 = false;
float _seg_duration = 0; float _seg_duration = 0;
uint32_t _seg_number = 0; uint32_t _seg_number = 0;
bool _seg_keep = false; bool _seg_keep = false;
......
...@@ -21,21 +21,14 @@ using namespace toolkit; ...@@ -21,21 +21,14 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
HlsMakerImp::HlsMakerImp(const string &m3u8_file, HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string &params, uint32_t bufSize, float seg_duration,
const string &params, uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) {
uint32_t bufSize,
float seg_duration,
uint32_t seg_number,
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
_poller = EventPollerPool::Instance().getPoller(); _poller = EventPollerPool::Instance().getPoller();
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/')); _path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
_path_hls = m3u8_file; _path_hls = m3u8_file;
_params = params; _params = params;
_buf_size = bufSize; _buf_size = bufSize;
_file_buf.reset(new char[bufSize], [](char *ptr) { _file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; });
delete[] ptr;
});
_info.folder = _path_prefix; _info.folder = _path_prefix;
} }
...@@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() { ...@@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() {
} }
void HlsMakerImp::clearCache(bool immediately, bool eof) { void HlsMakerImp::clearCache(bool immediately, bool eof) {
//录制完了 // 录制完了
flushLastSegment(eof); flushLastSegment(eof);
if (!isLive()||isKeep()) { if (!isLive() || isKeep()) {
return; return;
} }
...@@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) { ...@@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
_file = nullptr; _file = nullptr;
_segment_file_paths.clear(); _segment_file_paths.clear();
//hls直播才删除文件 // hls直播才删除文件
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
if (!delay || immediately) { if (!delay || immediately) {
File::delete_file(_path_prefix.data()); File::delete_file(_path_prefix.data());
...@@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { ...@@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
auto strDate = getTimeStr("%Y-%m-%d"); auto strDate = getTimeStr("%Y-%m-%d");
auto strHour = getTimeStr("%H"); auto strHour = getTimeStr("%H");
auto strTime = getTimeStr("%M-%S"); 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; segment_path = _path_prefix + "/" + segment_name;
if (isLive()) { if (isLive()) {
_segment_file_paths.emplace(index, segment_path); _segment_file_paths.emplace(index, segment_path);
...@@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { ...@@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
} }
_file = makeFile(segment_path, true); _file = makeFile(segment_path, true);
//保存本切片的元数据 // 保存本切片的元数据
_info.start_time = ::time(NULL); _info.start_time = ::time(NULL);
_info.file_name = segment_name; _info.file_name = segment_name;
_info.file_path = segment_path; _info.file_path = segment_path;
_info.url = _info.app + "/" + _info.stream + "/" + segment_name; _info.url = _info.app + "/" + _info.stream + "/" + segment_name;
if (!_file) { if (!_file) {
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg();
} }
if (_params.empty()) { if (_params.empty()) {
return segment_name; return segment_name;
...@@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) { ...@@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) {
_segment_file_paths.erase(it); _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) { void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
if (_file) { if (_file) {
fwrite(data, len, 1, _file.get()); fwrite(data, len, 1, _file.get());
...@@ -132,13 +137,12 @@ void HlsMakerImp::onWriteHls(const std::string &data) { ...@@ -132,13 +137,12 @@ void HlsMakerImp::onWriteHls(const std::string &data) {
_media_src->setIndexFile(data); _media_src->setIndexFile(data);
} }
} else { } 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) { void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
//关闭并flush文件到磁盘 // 关闭并flush文件到磁盘
_file = nullptr; _file = nullptr;
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
...@@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s ...@@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s
_info.app = app; _info.app = app;
_info.stream = stream_id; _info.stream = stream_id;
_info.vhost = vhost; _info.vhost = vhost;
_media_src = std::make_shared<HlsMediaSource>(_info); _media_src = std::make_shared<HlsMediaSource>(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info);
} }
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
return _media_src; return _media_src;
} }
}//namespace mediakit } // namespace mediakit
\ No newline at end of file \ No newline at end of file
...@@ -19,15 +19,10 @@ ...@@ -19,15 +19,10 @@
namespace mediakit { namespace mediakit {
class HlsMakerImp : public HlsMaker{ class HlsMakerImp : public HlsMaker {
public: public:
HlsMakerImp(const std::string &m3u8_file, HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string &params, uint32_t bufSize = 64 * 1024,
const std::string &params, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
uint32_t bufSize = 64 * 1024,
float seg_duration = 5,
uint32_t seg_number = 3,
bool seg_keep = false);
~HlsMakerImp() override; ~HlsMakerImp() override;
/** /**
...@@ -52,6 +47,7 @@ public: ...@@ -52,6 +47,7 @@ public:
protected: protected:
std::string onOpenSegment(uint64_t index) override ; std::string onOpenSegment(uint64_t index) override ;
void onDelSegment(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 onWriteSegment(const char *data, size_t len) override;
void onWriteHls(const std::string &data) override; void onWriteHls(const std::string &data) override;
void onFlushLastSegment(uint64_t duration_ms) override; void onFlushLastSegment(uint64_t duration_ms) override;
......
...@@ -25,7 +25,7 @@ public: ...@@ -25,7 +25,7 @@ public:
using RingType = toolkit::RingBuffer<std::string>; using RingType = toolkit::RingBuffer<std::string>;
using Ptr = std::shared_ptr<HlsMediaSource>; using Ptr = std::shared_ptr<HlsMediaSource>;
HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {} HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {}
~HlsMediaSource() override = default; ~HlsMediaSource() override = default;
/** /**
......
...@@ -13,27 +13,27 @@ ...@@ -13,27 +13,27 @@
#include "HlsMakerImp.h" #include "HlsMakerImp.h"
#include "MPEG.h" #include "MPEG.h"
#include "MP4Muxer.h"
#include "Common/config.h" #include "Common/config.h"
namespace mediakit { namespace mediakit {
class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this<HlsRecorder> { template <typename Muxer>
class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this<HlsRecorderBase<Muxer> > {
public: public:
using Ptr = std::shared_ptr<HlsRecorder>; HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string &params, const ProtocolOption &option) {
HlsRecorder(const std::string &m3u8_file, const std::string &params, const ProtocolOption &option) : MpegMuxer(false) {
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum); GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep);
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
_option = option; _option = option;
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); _hls = std::make_shared<HlsMakerImp>(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
//清空上次的残余文件 // 清空上次的残余文件
_hls->clearCache(); _hls->clearCache();
} }
~HlsRecorder() { MpegMuxer::flush(); }; ~HlsRecorderBase() override = default;
void setMediaSource(const MediaTuple& tuple) { void setMediaSource(const MediaTuple& tuple) {
_hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream); _hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream);
...@@ -41,7 +41,7 @@ public: ...@@ -41,7 +41,7 @@ public:
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) { void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
setDelegate(listener); setDelegate(listener);
_hls->getMediaSource()->setListener(shared_from_this()); _hls->getMediaSource()->setListener(this->shared_from_this());
} }
int readerCount() { return _hls->getMediaSource()->readerCount(); } int readerCount() { return _hls->getMediaSource()->readerCount(); }
...@@ -64,7 +64,7 @@ public: ...@@ -64,7 +64,7 @@ public:
_hls->getMediaSource()->setIndexFile(""); _hls->getMediaSource()->setIndexFile("");
} }
if (_enabled || !_option.hls_demand) { if (_enabled || !_option.hls_demand) {
return MpegMuxer::inputFrame(frame); return Muxer::inputFrame(frame);
} }
return false; return false;
} }
...@@ -74,20 +74,54 @@ public: ...@@ -74,20 +74,54 @@ public:
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true; return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
} }
protected:
bool _enabled = true;
bool _clear_cache = false;
ProtocolOption _option;
std::shared_ptr<HlsMakerImp> _hls;
};
class HlsRecorder final : public HlsRecorderBase<MpegMuxer> {
public:
using Ptr = std::shared_ptr<HlsRecorder>;
template <typename ...ARGS>
HlsRecorder(ARGS && ...args) : HlsRecorderBase<MpegMuxer>(false, std::forward<ARGS>(args)...) {}
~HlsRecorder() override { this->flush(); }
private: private:
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override { void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
if (!buffer) { if (!buffer) {
// reset tracks
_hls->inputData(nullptr, 0, timestamp, key_pos); _hls->inputData(nullptr, 0, timestamp, key_pos);
} else { } else {
_hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos);
} }
} }
};
class HlsFMP4Recorder final : public HlsRecorderBase<MP4MuxerMemory> {
public:
using Ptr = std::shared_ptr<HlsFMP4Recorder>;
template <typename ...ARGS>
HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase<MP4MuxerMemory>(true, std::forward<ARGS>(args)...) {}
~HlsFMP4Recorder() override { this->flush(); }
void addTrackCompleted() override {
HlsRecorderBase<MP4MuxerMemory>::addTrackCompleted();
auto data = getInitSegment();
_hls->inputInitSegment(data.data(), data.size());
}
private: private:
bool _enabled = true; void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override {
bool _clear_cache = false; if (buffer.empty()) {
ProtocolOption _option; // reset tracks
std::shared_ptr<HlsMakerImp> _hls; _hls->inputData(nullptr, 0, timestamp, key_pos);
} else {
_hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos);
}
}
}; };
}//namespace mediakit }//namespace mediakit
#endif //HLSRECORDER_H #endif //HLSRECORDER_H
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
* may be found in the AUTHORS file in the root of the source tree. * 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 "MP4.h"
#include "Util/File.h" #include "Util/File.h"
#include "Util/logger.h" #include "Util/logger.h"
...@@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){ ...@@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){
} }
}//namespace mediakit }//namespace mediakit
#endif //NABLE_MP4RECORD #endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#ifndef ZLMEDIAKIT_MP4_H #ifndef ZLMEDIAKIT_MP4_H
#define ZLMEDIAKIT_MP4_H #define ZLMEDIAKIT_MP4_H
#ifdef ENABLE_MP4 #if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include <memory> #include <memory>
#include <string> #include <string>
...@@ -136,5 +136,5 @@ private: ...@@ -136,5 +136,5 @@ private:
}; };
}//namespace mediakit }//namespace mediakit
#endif //NABLE_MP4RECORD #endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#endif //ZLMEDIAKIT_MP4_H #endif //ZLMEDIAKIT_MP4_H
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* may be found in the AUTHORS file in the root of the source tree. * 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 "MP4Muxer.h"
#include "Extension/AAC.h" #include "Extension/AAC.h"
...@@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) { ...@@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
return false; return false;
} }
bool key_frame = frame->keyFrame(); // flush切片
//flush切片
saveSegment(); saveSegment();
auto data = _memory_file->getAndClearMemory(); auto data = _memory_file->getAndClearMemory();
if (!data.empty()) { if (!data.empty()) {
//输出切片数据 // 输出切片数据
onSegmentData(std::move(data), frame->dts(), _key_frame); onSegmentData(std::move(data), _last_dst, _key_frame);
_key_frame = false; _key_frame = false;
} }
if (key_frame) { if (frame->keyFrame()) {
_key_frame = true; _key_frame = true;
} }
if (frame->getTrackType() == TrackVideo || !haveVideo()) {
_last_dst = frame->dts();
}
return MP4MuxerInterface::inputFrame(frame); return MP4MuxerInterface::inputFrame(frame);
} }
}//namespace mediakit }//namespace mediakit
#endif//#ifdef ENABLE_MP4 #endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
...@@ -11,13 +11,13 @@ ...@@ -11,13 +11,13 @@
#ifndef ZLMEDIAKIT_MP4MUXER_H #ifndef ZLMEDIAKIT_MP4MUXER_H
#define ZLMEDIAKIT_MP4MUXER_H #define ZLMEDIAKIT_MP4MUXER_H
#ifdef ENABLE_MP4 #if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include "Common/MediaSink.h" #include "Common/MediaSink.h"
#include "Common/Stamp.h" #include "Common/Stamp.h"
#include "MP4.h" #include "MP4.h"
namespace mediakit{ namespace mediakit {
class MP4MuxerInterface : public MediaSinkInterface { class MP4MuxerInterface : public MediaSinkInterface {
public: public:
...@@ -147,11 +147,39 @@ protected: ...@@ -147,11 +147,39 @@ protected:
private: private:
bool _key_frame = false; bool _key_frame = false;
uint64_t _last_dst = 0;
std::string _init_segment; std::string _init_segment;
MP4FileMemory::Ptr _memory_file; MP4FileMemory::Ptr _memory_file;
}; };
} // namespace mediakit
}//namespace mediakit #else
#endif//#ifdef ENABLE_MP4
#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 #endif //ZLMEDIAKIT_MP4MUXER_H
...@@ -25,7 +25,7 @@ namespace mediakit { ...@@ -25,7 +25,7 @@ namespace mediakit {
//该类用于产生MPEG-TS/MPEG-PS //该类用于产生MPEG-TS/MPEG-PS
class MpegMuxer : public MediaSinkInterface { class MpegMuxer : public MediaSinkInterface {
public: public:
MpegMuxer(bool is_ps); MpegMuxer(bool is_ps = false);
~MpegMuxer() override; ~MpegMuxer() override;
/** /**
...@@ -86,7 +86,7 @@ namespace mediakit { ...@@ -86,7 +86,7 @@ namespace mediakit {
class MpegMuxer : public MediaSinkInterface { class MpegMuxer : public MediaSinkInterface {
public: public:
MpegMuxer(bool is_ps) {} MpegMuxer(bool is_ps = false) {}
~MpegMuxer() override = default; ~MpegMuxer() override = default;
bool addTrack(const Track::Ptr &track) override { return false; } bool addTrack(const Track::Ptr &track) override { return false; }
void resetTracks() override {} void resetTracks() override {}
......
...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
#include "Common/MediaSource.h" #include "Common/MediaSource.h"
#include "MP4Recorder.h" #include "MP4Recorder.h"
#include "HlsRecorder.h" #include "HlsRecorder.h"
#include "Util/File.h" #include "FMP4/FMP4MediaSourceMuxer.h"
#include "TS/TSMediaSourceMuxer.h"
using namespace std; using namespace std;
using namespace toolkit; using namespace toolkit;
...@@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con ...@@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con
} }
return File::absolutePath(mp4FilePath, recordPath); 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: default:
return ""; return "";
} }
...@@ -82,6 +97,34 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const Me ...@@ -82,6 +97,34 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const Me
#endif #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<HlsFMP4Recorder>(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<FMP4MediaSourceMuxer>(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<TSMediaSourceMuxer>(tuple, option);
#else
throw std::invalid_argument("mpegts相关功能未打开,请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试");
#endif
}
default: throw std::invalid_argument("未知的录制类型"); default: throw std::invalid_argument("未知的录制类型");
} }
} }
......
...@@ -44,7 +44,13 @@ public: ...@@ -44,7 +44,13 @@ public:
// 录制hls // 录制hls
type_hls = 0, type_hls = 0,
// 录制MP4 // 录制MP4
type_mp4 = 1 type_mp4 = 1,
// 录制hls.fmp4
type_hls_fmp4 = 2,
// fmp4直播
type_fmp4 = 3,
// ts直播
type_ts = 4,
} type; } type;
/** /**
......
...@@ -44,7 +44,8 @@ public: ...@@ -44,7 +44,8 @@ public:
return _media_src->readerCount(); return _media_src->readerCount();
} }
void onAllTrackReady(){ void addTrackCompleted() override {
RtmpMuxer::addTrackCompleted();
makeConfigPacket(); makeConfigPacket();
_media_src->setMetaData(getMetadata()); _media_src->setMetaData(getMetadata());
} }
......
...@@ -44,7 +44,8 @@ public: ...@@ -44,7 +44,8 @@ public:
_media_src->setTimeStamp(stamp); _media_src->setTimeStamp(stamp);
} }
void onAllTrackReady(){ void addTrackCompleted() override {
RtspMuxer::addTrackCompleted();
_media_src->setSdp(getSdp()); _media_src->setSdp(getSdp());
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论