新增支持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>
This commit is contained in:
XiaoYan Lin 2023-07-02 12:02:33 +08:00 committed by GitHub
parent 6aa4b741a3
commit cb0579a16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 413 additions and 224 deletions

View File

@ -47,19 +47,14 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
# TODO:
# movflv 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)
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
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
@ -72,18 +67,23 @@ if(ENABLE_MP4)
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
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
PUBLIC
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
update_cached_list(MK_LINK_LIBRARIES
MediaServer::flv MediaServer::mov)
update_cached_list(MK_COMPILE_DEFINITIONS
ENABLE_MP4)
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
@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> 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<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) {
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
}
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) {
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option);
_fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
}
#endif
//音频相关设置
enableAudio(option.enable_audio);
@ -128,14 +130,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
if (_ts) {
_ts->setListener(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<Listener> &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,6 +216,46 @@ 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<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;
}
}
@ -222,12 +263,12 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//此函数可能跨线程调用
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;
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

View File

@ -164,18 +164,14 @@ private:
toolkit::Ticker _last_check;
Stamp _stamp[2];
std::weak_ptr<Listener> _track_listener;
#if defined(ENABLE_RTPPROXY)
std::unordered_map<std::string, RingType::RingReader::Ptr> _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;

View File

@ -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;

View File

@ -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

View File

@ -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__"

View File

@ -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

View File

@ -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<SockInfoImp>();
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
if (end_with(file_path, kHlsSuffix)) {
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
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());

View File

@ -8,6 +8,7 @@
* may be found in the AUTHORS file in the root of the source tree.
*/
#include <iomanip>
#include "HlsMaker.h"
#include "Common/config.h"
@ -15,70 +16,63 @@ 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;
//最小允许设置为00个切片代表点播
_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;
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) {
// 录像点播支持时移
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);
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) {
@ -87,7 +81,7 @@ void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr
}
if (!_last_file_name.empty()) {
// 存在切片才写入ts数据
onWriteSegment((char *) data, len);
onWriteSegment(data, len);
_last_timestamp = timestamp;
}
} else {
@ -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;

View File

@ -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;

View File

@ -21,21 +21,14 @@ using namespace toolkit;
namespace mediakit {
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
const string &params,
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 &params, 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;
}
@ -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);
@ -97,7 +90,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
_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,9 +137,8 @@ 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) {
@ -166,7 +170,7 @@ 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<HlsMediaSource>(_info);
_media_src = std::make_shared<HlsMediaSource>(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info);
}
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {

View File

@ -21,13 +21,8 @@ namespace mediakit {
class HlsMakerImp : public HlsMaker {
public:
HlsMakerImp(const std::string &m3u8_file,
const std::string &params,
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 &params, 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;

View File

@ -25,7 +25,7 @@ public:
using RingType = toolkit::RingBuffer<std::string>;
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;
/**

View File

@ -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<HlsRecorder> {
template <typename Muxer>
class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this<HlsRecorderBase<Muxer> > {
public:
using Ptr = std::shared_ptr<HlsRecorder>;
HlsRecorder(const std::string &m3u8_file, const std::string &params, const ProtocolOption &option) : MpegMuxer(false) {
HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string &params, 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<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
_hls = std::make_shared<HlsMakerImp>(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<MediaSourceEvent> &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<toolkit::Buffer> 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<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:
void onWrite(std::shared_ptr<toolkit::Buffer> 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<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:
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

View File

@ -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)

View File

@ -11,7 +11,7 @@
#ifndef ZLMEDIAKIT_MP4_H
#define ZLMEDIAKIT_MP4_H
#ifdef ENABLE_MP4
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#include <memory>
#include <string>
@ -136,5 +136,5 @@ private:
};
}//namespace mediakit
#endif //NABLE_MP4RECORD
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#endif //ZLMEDIAKIT_MP4_H

View File

@ -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切片
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)

View File

@ -11,7 +11,7 @@
#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"
@ -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
#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//#ifdef ENABLE_MP4
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
#endif //ZLMEDIAKIT_MP4MUXER_H

View File

@ -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 {}

View File

@ -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<MediaSinkInterface> 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<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("未知的录制类型");
}
}

View File

@ -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;
/**

View File

@ -44,7 +44,8 @@ public:
return _media_src->readerCount();
}
void onAllTrackReady(){
void addTrackCompleted() override {
RtmpMuxer::addTrackCompleted();
makeConfigPacket();
_media_src->setMetaData(getMetadata());
}

View File

@ -44,7 +44,8 @@ public:
_media_src->setTimeStamp(stamp);
}
void onAllTrackReady(){
void addTrackCompleted() override {
RtspMuxer::addTrackCompleted();
_media_src->setSdp(getSdp());
}