diff --git a/conf/config.ini b/conf/config.ini index d690b1f8..302fb295 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -135,6 +135,8 @@ segDur=2 #m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个) #如果设置为0,则不删除切片,而是保存为点播 segNum=3 +#HLS切片延迟个数,大于0将生成hls_delay.m3u8文件,0则不生成 +segDelay=0 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 #是否广播 hls切片(ts/fmp4)完成通知(on_record_ts) diff --git a/src/Common/config.cpp b/src/Common/config.cpp index c345350d..357a9689 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -307,6 +307,7 @@ namespace Hls { const string kSegmentDuration = HLS_FIELD "segDur"; const string kSegmentNum = HLS_FIELD "segNum"; const string kSegmentKeep = HLS_FIELD "segKeep"; +const string kSegmentDelay = HLS_FIELD "segDelay"; const string kSegmentRetain = HLS_FIELD "segRetain"; const string kFileBufSize = HLS_FIELD "fileBufSize"; const string kBroadcastRecordTs = HLS_FIELD "broadcastRecordTs"; @@ -317,6 +318,7 @@ static onceToken token([]() { mINI::Instance()[kSegmentDuration] = 2; mINI::Instance()[kSegmentNum] = 3; mINI::Instance()[kSegmentKeep] = false; + mINI::Instance()[kSegmentDelay] = 0; mINI::Instance()[kSegmentRetain] = 5; mINI::Instance()[kFileBufSize] = 64 * 1024; mINI::Instance()[kBroadcastRecordTs] = false; diff --git a/src/Common/config.h b/src/Common/config.h index 7f9073d9..a6d7af4d 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -349,6 +349,8 @@ extern const std::string kSegmentDuration; extern const std::string kSegmentNum; // 如果设置为0,则不保留切片,设置为1则一直保留切片 extern const std::string kSegmentKeep; +// HLS切片延迟个数,大于0将生成hls_delay.m3u8文件,0则不生成 +extern const std::string kSegmentDelay; // HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 extern const std::string kSegmentRetain; // HLS文件写缓存大小 diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index 2b074e1b..11d5190a 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -24,15 +24,40 @@ HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool s _seg_keep = seg_keep; } -void HlsMaker::makeIndexFile(bool eof) { +void HlsMaker::makeIndexFile(bool include_delay, bool eof) { + GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay); + GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); + std::deque> temp(_seg_dur_list); + if (!include_delay) { + while (temp.size() > _seg_number) { + temp.pop_front(); + } + } int maxSegmentDuration = 0; - for (auto &tp : _seg_dur_list) { + for (auto &tp : temp) { 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; + uint64_t index_seq; + if (_seg_number) { + if (include_delay) { + if (_file_index > _seg_number + segDelay) { + index_seq = _file_index - _seg_number - segDelay; + } else { + index_seq = 0LL; + } + } else { + if (_file_index > _seg_number) { + index_seq = _file_index - _seg_number; + } else { + index_seq = 0LL; + } + } + } else { + index_seq = 0LL; + } string index_str; index_str.reserve(2048); @@ -50,7 +75,7 @@ void HlsMaker::makeIndexFile(bool eof) { } stringstream ss; - for (auto &tp : _seg_dur_list) { + for (auto &tp : temp) { ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n"; } index_str += ss.str(); @@ -58,7 +83,7 @@ void HlsMaker::makeIndexFile(bool eof) { if (eof) { index_str += "#EXT-X-ENDLIST\n"; } - onWriteHls(index_str); + onWriteHls(index_str, include_delay); } void HlsMaker::inputInitSegment(const char *data, size_t len) { @@ -91,12 +116,13 @@ void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool } void HlsMaker::delOldSegment() { + GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay); if (_seg_number == 0) { //如果设置为保留0个切片,则认为是保存为点播 return; } //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 - if (_file_index > _seg_number) { + if (_file_index > _seg_number + segDelay) { _seg_dur_list.pop_front(); } //如果设置为一直保存,就不删除 @@ -105,8 +131,8 @@ void HlsMaker::delOldSegment() { } GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 - if (_file_index > _seg_number + segRetain) { - onDelSegment(_file_index - _seg_number - segRetain - 1); + if (_file_index > _seg_number + segDelay + segRetain) { + onDelSegment(_file_index - _seg_number - segDelay - segRetain - 1); } } @@ -125,6 +151,7 @@ void HlsMaker::addNewSegment(uint64_t stamp) { } void HlsMaker::flushLastSegment(bool eof){ + GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay); if (_last_file_name.empty()) { //不存在上个切片 return; @@ -139,7 +166,11 @@ void HlsMaker::flushLastSegment(bool eof){ //先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 onFlushLastSegment(seg_dur); //然后写m3u8文件 - makeIndexFile(eof); + makeIndexFile(false, eof); + //写入切片延迟的m3u8文件 + if (segDelay) { + makeIndexFile(true, eof); + } } bool HlsMaker::isLive() const { diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index c06117e7..a95e2296 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -96,7 +96,7 @@ protected: /** * 写m3u8文件回调 */ - virtual void onWriteHls(const std::string &data) = 0; + virtual void onWriteHls(const std::string &data, bool include_delay) = 0; /** * 上一个 ts 切片写入完成, 可在这里进行通知处理 @@ -115,7 +115,7 @@ private: * 生成m3u8文件 * @param eof true代表点播 */ - void makeIndexFile(bool eof = false); + void makeIndexFile(bool include_delay, bool eof = false); /** * 删除旧的ts切片 diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index 7fc2b000..519b3d50 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -21,11 +21,20 @@ using namespace toolkit; namespace mediakit { +std::string getDelayPath(const std::string& originalPath) { + std::size_t pos = originalPath.find(".m3u8"); + if (pos != std::string::npos) { + return originalPath.substr(0, pos) + "_delay.m3u8"; + } + return originalPath; +} + 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; + _path_hls_delay = getDelayPath(m3u8_file); _params = params; _buf_size = bufSize; _file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; }); @@ -62,6 +71,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) { { std::list lst; lst.emplace_back(_path_hls); + lst.emplace_back(_path_hls_delay); if (!_path_init.empty()) { lst.emplace_back(_path_init); } @@ -146,8 +156,9 @@ void HlsMakerImp::onWriteSegment(const char *data, size_t len) { } } -void HlsMakerImp::onWriteHls(const std::string &data) { - auto hls = makeFile(_path_hls); +void HlsMakerImp::onWriteHls(const std::string &data, bool include_delay) { + auto path = include_delay ? _path_hls_delay : _path_hls; + auto hls = makeFile(path); if (hls) { fwrite(data.data(), data.size(), 1, hls.get()); hls.reset(); @@ -155,7 +166,7 @@ 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 << " " << get_uv_errmsg(); } } diff --git a/src/Record/HlsMakerImp.h b/src/Record/HlsMakerImp.h index 7d268a53..b3bf77b9 100644 --- a/src/Record/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -49,7 +49,7 @@ protected: 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 onWriteHls(const std::string &data, bool include_delay) override; void onFlushLastSegment(uint64_t duration_ms) override; private: @@ -60,6 +60,7 @@ private: int _buf_size; std::string _params; std::string _path_hls; + std::string _path_hls_delay; std::string _path_init; std::string _path_prefix; RecordInfo _info;