From e5f43df67bfa5575cba2362a8f4ecb3ca0cf874a Mon Sep 17 00:00:00 2001 From: renlu Date: Wed, 14 Sep 2022 15:03:10 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E6=8F=90=E4=BA=A4=EF=BC=8C=E5=9C=A8TS?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AF=B9=E5=BA=94=E7=9A=84=E5=B9=B4=E6=9C=88?= =?UTF-8?q?=E6=97=A5=E4=BB=A5=E5=8F=8A=E5=B0=8F=E6=97=B6=E4=B8=8B=E9=9D=A2?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=AF=B9=E5=BA=94=E7=9A=84m3u8=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/MultiMediaSourceMuxer.cpp | 16 +- src/Record/HlsMakerImpSub.cpp | 183 +++++++++++++++++ src/Record/HlsMakerImpSub.h | 79 +++++++ src/Record/HlsMakerSub.cpp | 296 +++++++++++++++++++++++++++ src/Record/HlsMakerSub.h | 142 +++++++++++++ src/Record/HlsRecorder.h | 15 +- src/Record/MP4Recorder.cpp | 10 +- 7 files changed, 727 insertions(+), 14 deletions(-) create mode 100644 src/Record/HlsMakerImpSub.cpp create mode 100644 src/Record/HlsMakerImpSub.h create mode 100644 src/Record/HlsMakerSub.cpp create mode 100644 src/Record/HlsMakerSub.h diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index a6118f61..3461ab5a 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -202,18 +202,17 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { switch (type) { case Recorder::type_hls : { - if (start && !_hls) { - //开始录制 + if (!_hls) + { + //创建hls对象 auto hls = dynamic_pointer_cast(makeRecorder(sender, getTracks(), type, custom_path, max_second)); if (hls) { //设置HlsMediaSource的事件监听器 hls->setListener(shared_from_this()); } _hls = hls; - } else if (!start && _hls) { - //停止录制 - _hls = nullptr; } + _hls->startRecord(start); return true; } case Recorder::type_mp4 : { @@ -234,7 +233,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; + //return !!_hls; + if (_hls){ + return _hls->getRecordFlag(); + }else{ + return false; + } case Recorder::type_mp4 : return !!_mp4; default: diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp new file mode 100644 index 00000000..0f77d366 --- /dev/null +++ b/src/Record/HlsMakerImpSub.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "HlsMakerImpSub.h" +#include "Util/util.h" +#include "Util/uv_errno.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +HlsMakerImpSub::HlsMakerImpSub( + const string &m3u8_file, + const string ¶ms, + uint32_t bufSize, + float seg_duration, + uint32_t seg_number, + bool seg_keep):HlsMakerSub(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; + }); + + _info.folder = _path_prefix; +} + +HlsMakerImpSub::~HlsMakerImpSub() { + clearCache(false, true); +} + +void HlsMakerImpSub::clearCache() { + clearCache(true, false); +} + +void HlsMakerImpSub::clearCache(bool immediately, bool eof) { + //录制完了 + flushLastSegment(eof); + if (!isLive()||isKeep()) { + return; + } + + clear(); + _file = nullptr; + _segment_file_paths.clear(); + + //删除缓存的m3u8文件 + File::delete_file((_path_prefix + "/hls.m3u8").data() ); + + ////hls直播才删除文件 + //GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); + //if (!delay || immediately) { + // File::delete_file(_path_prefix.data()); + //} else { + // auto path_prefix = _path_prefix; + // _poller->doDelayTask(delay * 1000, [path_prefix]() { + // File::delete_file(path_prefix.data()); + // return 0; + // }); + //} +} + +string HlsMakerImpSub::onOpenSegment(uint64_t index) { + string segment_name, segment_path; + + auto strDate = getTimeStr("%Y-%m-%d"); + auto strHour = getTimeStr("%H"); + auto strTime = getTimeStr("%M-%S"); + segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts"; + segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name; + if (isLive()) { + + GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); + GET_CONFIG(uint32_t, segKeep, Hls::kSegmentNum); + if (_segment_file_paths.size() > segRetain + segKeep) { + _segment_file_paths.erase(index - segRetain - segKeep -1); + } + _segment_file_paths.emplace(index, segment_path); + } + + _file = makeFile(segment_path, true); + + //保存本切片的元数据 + _info.start_time = ::time(NULL); + _info.file_name = segment_name; + _info.file_path = segment_path; + _info.url = _info.app + "/" + _info.stream + "/" + segment_name; + + if (!_file) { + WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); + } + if (_params.empty()) { + return strDate + "/" + strHour + "/" + segment_name; + } + return strDate + "/" + strHour + "/" + segment_name + "?" + _params; +} + +void HlsMakerImpSub::onDelSegment(uint64_t index) { + auto it = _segment_file_paths.find(index); + if (it == _segment_file_paths.end()) { + return; + } + File::delete_file(it->second.data()); + _segment_file_paths.erase(it); +} + +void HlsMakerImpSub::onWriteSegment(const char *data, size_t len) { + if (_file) { + fwrite(data, len, 1, _file.get()); + } + if (_media_src) { + _media_src->onSegmentSize(len); + } +} + +void HlsMakerImpSub::onWriteHls(const std::string &data) { + auto hls = makeFile(_path_hls); + if (hls) { + fwrite(data.data(), data.size(), 1, hls.get()); + hls.reset(); + if (_media_src) { + _media_src->setIndexFile(data); + } + } else { + WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); + } + //DebugL << "\r\n" << string(data,len); +} + +void HlsMakerImpSub::onFlushLastSegment(uint64_t duration_ms) { + //关闭并flush文件到磁盘 + _file = nullptr; + + GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); + if (broadcastRecordTs) { + _info.time_len = duration_ms / 1000.0f; + _info.file_size = File::fileSize(_info.file_path.data()); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); + } +} + +std::shared_ptr HlsMakerImpSub::makeFile(const string &file, bool setbuf) { + auto file_buf = _file_buf; + auto ret = shared_ptr(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) { + if (fp) { + fclose(fp); + } + }); + if (ret && setbuf) { + setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size); + } + return ret; +} + +void HlsMakerImpSub::setMediaSource(const string &vhost, const string &app, const string &stream_id) { + _media_src = std::make_shared(vhost, app, stream_id); + _info.app = app; + _info.stream = stream_id; + _info.vhost = vhost; +} + +HlsMediaSource::Ptr HlsMakerImpSub::getMediaSource() const { + return _media_src; +} + +std::string HlsMakerImpSub::getPathPrefix() { + return _path_prefix; +} + +}//namespace mediakit \ No newline at end of file diff --git a/src/Record/HlsMakerImpSub.h b/src/Record/HlsMakerImpSub.h new file mode 100644 index 00000000..6a9d7fdd --- /dev/null +++ b/src/Record/HlsMakerImpSub.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HLSMAKERIMPSUB_H +#define HLSMAKERIMPSUB_H + +#include +#include +#include +#include "HlsMakerSub.h" +#include "HlsMediaSource.h" + +namespace mediakit { + +class HlsMakerImpSub : public HlsMakerSub{ +public: + HlsMakerImpSub( + const std::string &m3u8_file, + const std::string ¶ms, + uint32_t bufSize = 64 * 1024, + float seg_duration = 5, + uint32_t seg_number = 3, + bool seg_keep = false); + + ~HlsMakerImpSub() override; + + /** + * 设置媒体信息 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + */ + void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id); + + /** + * 获取MediaSource + * @return + */ + HlsMediaSource::Ptr getMediaSource() const; + + /** + * 清空缓存 + */ + void clearCache(); + +protected: + std::string onOpenSegment(uint64_t index) override ; + void onDelSegment(uint64_t index) override; + void onWriteSegment(const char *data, size_t len) override; + void onWriteHls(const std::string &data) override; + void onFlushLastSegment(uint64_t duration_ms) override; + std::string getPathPrefix() override; + +private: + std::shared_ptr makeFile(const std::string &file,bool setbuf = false); + void clearCache(bool immediately, bool eof); + +private: + int _buf_size; + std::string _params; + std::string _path_hls; + std::string _path_prefix; + RecordInfo _info; + std::shared_ptr _file; + std::shared_ptr _file_buf; + HlsMediaSource::Ptr _media_src; + toolkit::EventPoller::Ptr _poller; + +}; + +}//namespace mediakit +#endif //HLSMAKERIMPSUB_H diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp new file mode 100644 index 00000000..7825eb4d --- /dev/null +++ b/src/Record/HlsMakerSub.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + + #include "Util/File.h" +#include "HlsMakerSub.h" +using namespace std; +using namespace toolkit; + +namespace mediakit { + +HlsMakerSub::HlsMakerSub(float seg_duration, uint32_t seg_number, bool seg_keep) { + //最小允许设置为0,0个切片代表点播 + _seg_number = seg_number; + _seg_duration = seg_duration; + _seg_keep = seg_keep; + _is_record = false; +} + +HlsMakerSub::~HlsMakerSub() { + + _is_close_stream = true; +} + +void HlsMakerSub::startRecord(bool isRecord) { + if (_is_record) {//检测到上一次是在录像,清空_segment_file_paths + _segment_file_paths.clear(); + } + + if(isRecord) { + _seg_keep = true; + }else{ + _seg_keep = false; + _is_close_stream = true; + } + _is_record = isRecord; +} + + +void HlsMakerSub::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 sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; + + string m3u8; + if (_seg_number == 0) { + // 录像点播支持时移 + snprintf(file_content, sizeof(file_content), + "#EXTM3U\n" + "#EXT-X-PLAYLIST-TYPE:EVENT\n" + "#EXT-X-VERSION:4\n" + "#EXT-X-TARGETDURATION:%u\n" + "#EXT-X-MEDIA-SEQUENCE:%llu\n", + (maxSegmentDuration + 999) / 1000, + sequence); + } 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); + } + + m3u8.assign(file_content); + + 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); + } + + if (eof) { + snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); + m3u8.append(file_content); + } + onWriteHls(m3u8); +} + + +void HlsMakerSub::inputData(void *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; + _last_seg_timestamp = _last_timestamp = timestamp; + } + if (is_idr_fast_packet) { + //尝试切片ts + addNewSegment(timestamp); + } + if (!_last_file_name.empty()) { + //存在切片才写入ts数据 + onWriteSegment((char *) data, len); + _last_timestamp = timestamp; + } + } else { + //resetTracks时触发此逻辑 + flushLastSegment(false); + } +} + +void HlsMakerSub::delOldSegment() { + if (_seg_number == 0) { + //如果设置为保留0个切片,则认为是保存为点播 + return; + } + //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 + if (_file_index > _seg_number) { + _seg_dur_list.pop_front(); + } + //如果设置为一直保存,就不删除 + if (_seg_keep) { + return; + } + GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); + //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 + if (_file_index > _seg_number + segRetain) { + onDelSegment(_file_index - _seg_number - segRetain - 1); + } +} + +void HlsMakerSub::addNewSegment(uint64_t stamp) { + if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) { + //存在上个切片,并且未到分片时间 + return; + } + + //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 + flushLastSegment(false); + + //新增切片 + _last_file_name = onOpenSegment(_file_index++); + //记录本次切片的起始时间戳 + _last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; +} + +void HlsMakerSub::flushLastSegment(bool eof) { + if (_last_file_name.empty()) { + //不存在上个切片 + return; + } + //文件创建到最后一次数据写入的时间即为切片长度 + auto seg_dur = _last_timestamp - _last_seg_timestamp; + if (seg_dur <= 0) { + seg_dur = 100; + } + _seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name)); + delOldSegment(); + //先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 + onFlushLastSegment(seg_dur); + //然后写m3u8文件 + makeIndexFile(eof); + //判断当前是否在录像,正在录像的话,生成录像的m3u8文件 + if (_is_record) { + createM3u8FileForRecord(); + } +} + +bool HlsMakerSub::isLive() { + return _seg_number != 0; +} + +bool HlsMakerSub::isKeep() { + return _seg_keep; +} + +void HlsMakerSub::clear() { + _file_index = 0; + _last_timestamp = 0; + _last_seg_timestamp = 0; + _seg_dur_list.clear(); + _last_file_name.clear(); +} + +std::string HlsMakerSub::getM3u8TSDuration(const std::string &file_content) { + + size_t begin = file_content.find("#EXT-X-TARGETDURATION:"); + size_t end = file_content.find("#EXT-X-MEDIA-SEQUENCE"); + string duration = file_content.substr(begin, end - begin); + return duration; +} + +std::string HlsMakerSub::getM3u8TSBody(const std::string &file_content) { + + string new_file = file_content; + if (file_content.find("#EXT-X-ENDLIST") != file_content.npos) { + //找到了,则去掉"#EXT-X-ENDLIST" + new_file = file_content.substr(0, file_content.length() - 15); + } + + string body = new_file.substr(new_file.find_last_of("#")); + //此时的body为 + //#EXTINF:4.534, + //2022-09-14/08/2022-09-14_08-35-16.ts + string extinf = body.substr(0, body.find(",")+2); + string tsFile = body.substr(body.find_last_of("/") + 1); + body.append("#EXT-X-ENDLIST\n"); + + return extinf + tsFile + "#EXT-X-ENDLIST\n"; +} + +std::string HlsMakerSub::getTsFile(const std::string &file_content) { + // 最后一个TS的body为 + // 2022-09-13/13/58-13_43.ts + // #EXT-X-ENDLIST + string body = file_content.substr(file_content.find_last_of(",") + 2); + string ts_file_name = body.substr(body.find_last_of("/") + 1); + if (ts_file_name.find("#EXT-X-ENDLIST") == ts_file_name.npos ) { + ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 4); //没找到,去掉“.ts\n”,只留名字 + } else { + ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 19); //找到的话,去掉“.ts\n#EXT-X-ENDLIST\n”,只留名字 + } + + return ts_file_name; +} + +void HlsMakerSub::createM3u8FileForRecord() { + // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长 + string file_str = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); + if (file_str.empty()) { + return; + } + string duration = getM3u8TSDuration(file_str); + string body = getM3u8TSBody(file_str); + string ts_file_name = getTsFile(file_str); //ts_file: 2022-09-14_11-06-03 + string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/"; + + + // 2.判断该目录下有没有m3u8文件,没有的话,生成第一个m3u8文件,有的话,重命名 + File::scanDir( + m3u8_file, + [this](const string &path, bool isDir) -> bool { + if (!isDir && end_with(path, ".m3u8")) { + _m3u8_file_num++; + } + return true; + }, + false); + + if (_m3u8_file_num == 0) { //第一次播放流 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } else { //断流过,一次以上播放 + if (_is_close_stream) { + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } + if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + } + } + + _m3u8_file_num = 0; + + //3.写m3u8文件 + string m3u8Header = "#EXTM3U\n" + "#EXT-X-PLAYLIST-TYPE:EVENT\n" + "#EXT-X-VERSION:4\n"; + file_str = File::loadFile(_m3u8_file_path.data()); + //第一次进来,是空文件。 + if (file_str == "") { + auto file = File::create_file(_m3u8_file_path.data(), "wb"); + if (file) { + fwrite(m3u8Header.data(), m3u8Header.size(), 1, file); + fwrite(duration.data(), duration.size(), 1, file); + fwrite("#EXT-X-MEDIA-SEQUENCE:0\n", string("#EXT-X-MEDIA-SEQUENCE:0\n").size(), 1, file); + fwrite(body.data(), body.size(), 1, file); + fclose(file); + } + } else { + //第二次进来,读取文件的内容,修改duration,追加body,保存文件 + string old_duration = getM3u8TSDuration(file_str); + replace(file_str, old_duration, duration); + string new_str = file_str.substr(0, file_str.length() - 15); + new_str.append(body); + File::saveFile(new_str, _m3u8_file_path.data()); + } +} + +}//namespace mediakit diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h new file mode 100644 index 00000000..5c6491b2 --- /dev/null +++ b/src/Record/HlsMakerSub.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HLSMAKERSUB_H +#define HLSMAKERSUB_H + +#include +#include +#include "Common/config.h" +#include "Util/TimeTicker.h" +#include "Util/File.h" +#include "Util/util.h" +#include "Util/logger.h" + +namespace mediakit { + +class HlsMakerSub { +public: + /** + * @param seg_duration 切片文件长度 + * @param seg_number 切片个数 + * @param seg_keep 是否保留切片文件 + */ + HlsMakerSub(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); + virtual ~HlsMakerSub(); + + /** + * 写入ts数据 + * @param data 数据 + * @param len 数据长度 + * @param timestamp 毫秒时间戳 + * @param is_idr_fast_packet 是否为关键帧第一个包 + */ + void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + + /** + * 是否为直播 + */ + bool isLive(); + + /** + * 是否保留切片文件 + */ + bool isKeep(); + + /** + * 清空记录 + */ + void clear(); + //设置是否录像标志 + void startRecord(bool isRecord); + +protected: + /** + * 创建ts切片文件回调 + * @param index + * @return + */ + virtual std::string onOpenSegment(uint64_t index) = 0; + + /** + * 删除ts切片文件回调 + * @param index + */ + virtual void onDelSegment(uint64_t index) = 0; + + /** + * 写ts切片文件回调 + * @param data + * @param len + */ + virtual void onWriteSegment(const char *data, size_t len) = 0; + + /** + * 写m3u8文件回调 + */ + virtual void onWriteHls(const std::string &data) = 0; + + /** + * 上一个 ts 切片写入完成, 可在这里进行通知处理 + * @param duration_ms 上一个 ts 切片的时长, 单位为毫秒 + */ + virtual void onFlushLastSegment(uint64_t duration_ms) {}; + virtual std::string getPathPrefix() = 0; + + /** + * 关闭上个ts切片并且写入m3u8索引 + * @param eof HLS直播是否已结束 + */ + void flushLastSegment(bool eof); + +private: + /** + * 生成m3u8文件 + * @param eof true代表点播 + */ + void makeIndexFile(bool eof = false); + + /** + * 删除旧的ts切片 + */ + void delOldSegment(); + + /** + * 添加新的ts切片 + * @param timestamp + */ + void addNewSegment(uint64_t timestamp); + + //新增函数,实现录像功能 + std::string getTsFile(const std::string &file_content); + std::string getM3u8TSDuration(const std::string &file_content); + std::string getM3u8TSBody(const std::string &file_content); + void createM3u8FileForRecord(); + +private: + float _seg_duration = 0; + uint32_t _seg_number = 0; + bool _seg_keep = false; + uint64_t _last_timestamp = 0; + uint64_t _last_seg_timestamp = 0; + uint64_t _file_index = 0; + std::string _last_file_name; + std::deque > _seg_dur_list; + bool _is_record = false; + bool _is_close_stream = false; + int _m3u8_file_num = 0; + std::string _m3u8_file_path; + +public: + std::map _segment_file_paths; +}; + +}//namespace mediakit +#endif //HLSMAKERSUB_H diff --git a/src/Record/HlsRecorder.h b/src/Record/HlsRecorder.h index c5b50f45..f3ddfcc0 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -11,7 +11,8 @@ #ifndef HLSRECORDER_H #define HLSRECORDER_H -#include "HlsMakerImp.h" +//#include "HlsMakerImp.h" +#include "HlsMakerImpSub.h" #include "MPEG.h" namespace mediakit { @@ -25,7 +26,8 @@ public: GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); - _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + // _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); //清空上次的残余文件 _hls->clearCache(); } @@ -73,7 +75,12 @@ public: //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 return hls_demand ? (_clear_cache ? true : _enabled) : true; } + void startRecord(bool flag) { + _hls->startRecord(flag); + _isRecord = flag; + } + bool getRecordFlag() { return _isRecord; } private: void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { if (!buffer) { @@ -86,7 +93,9 @@ private: private: bool _enabled = true; bool _clear_cache = false; - std::shared_ptr _hls; + //std::shared_ptr _hls; + std::shared_ptr _hls; + bool _isRecord = false; }; }//namespace mediakit #endif //HLSRECORDER_H diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index 35dbeaa0..af955e7a 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -33,11 +33,11 @@ MP4Recorder::MP4Recorder(const string &path, const string &vhost, const string & } MP4Recorder::~MP4Recorder() { - closeFile(); + //closeFile(); } void MP4Recorder::createFile() { - closeFile(); + //closeFile(); auto date = getTimeStr("%Y-%m-%d"); auto time = getTimeStr("%H-%M-%S"); auto full_path_tmp = _folder_path + date + "/." + time + ".mp4"; @@ -93,7 +93,7 @@ void MP4Recorder::asyncClose() { void MP4Recorder::closeFile() { if (_muxer) { - asyncClose(); + //asyncClose(); _muxer = nullptr; } } @@ -113,7 +113,7 @@ bool MP4Recorder::inputFrame(const Frame::Ptr &frame) { // 2、到了切片时间,并且只有音频 // 3、到了切片时间,有视频并且遇到视频的关键帧 _last_dts = 0; - createFile(); + //createFile(); } } @@ -134,7 +134,7 @@ bool MP4Recorder::addTrack(const Track::Ptr &track) { } void MP4Recorder::resetTracks() { - closeFile(); + //closeFile(); _tracks.clear(); _have_video = false; } From a0f1e11208b0654ebe7b8e741e158626f614b945 Mon Sep 17 00:00:00 2001 From: renlu Date: Thu, 15 Sep 2022 17:15:33 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E7=B2=BE=E7=AE=80=E7=94=9F=E6=88=90m3u8?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=83=A8=E5=88=86=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerSub.cpp | 49 +++++++++++++++++++------------------- src/Record/HlsMakerSub.h | 1 - 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index 7825eb4d..ba114f72 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -10,6 +10,14 @@ #include "Util/File.h" #include "HlsMakerSub.h" +#if defined(_WIN32) +#include +#define _access access +#else +#include +#endif // WIN32 + + using namespace std; using namespace toolkit; @@ -188,14 +196,6 @@ void HlsMakerSub::clear() { _last_file_name.clear(); } -std::string HlsMakerSub::getM3u8TSDuration(const std::string &file_content) { - - size_t begin = file_content.find("#EXT-X-TARGETDURATION:"); - size_t end = file_content.find("#EXT-X-MEDIA-SEQUENCE"); - string duration = file_content.substr(begin, end - begin); - return duration; -} - std::string HlsMakerSub::getM3u8TSBody(const std::string &file_content) { string new_file = file_content; @@ -232,13 +232,13 @@ std::string HlsMakerSub::getTsFile(const std::string &file_content) { void HlsMakerSub::createM3u8FileForRecord() { // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长 - string file_str = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); - if (file_str.empty()) { + string live_file = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); + if (live_file.empty()) { return; } - string duration = getM3u8TSDuration(file_str); - string body = getM3u8TSBody(file_str); - string ts_file_name = getTsFile(file_str); //ts_file: 2022-09-14_11-06-03 + + string body = getM3u8TSBody(live_file); + string ts_file_name = getTsFile(live_file); // ts_file: 2022-09-14_11-06-03 string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/"; @@ -271,25 +271,24 @@ void HlsMakerSub::createM3u8FileForRecord() { //3.写m3u8文件 string m3u8Header = "#EXTM3U\n" "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n"; - file_str = File::loadFile(_m3u8_file_path.data()); - //第一次进来,是空文件。 - if (file_str == "") { + "#EXT-X-VERSION:4\n" + "#EXT-X-TARGETDURATION:2\n" + "#EXT-X-MEDIA-SEQUENCE:0\n"; + if (access(_m3u8_file_path.data(), 0) != 0) { //文件不存在 auto file = File::create_file(_m3u8_file_path.data(), "wb"); if (file) { fwrite(m3u8Header.data(), m3u8Header.size(), 1, file); - fwrite(duration.data(), duration.size(), 1, file); - fwrite("#EXT-X-MEDIA-SEQUENCE:0\n", string("#EXT-X-MEDIA-SEQUENCE:0\n").size(), 1, file); fwrite(body.data(), body.size(), 1, file); fclose(file); } } else { - //第二次进来,读取文件的内容,修改duration,追加body,保存文件 - string old_duration = getM3u8TSDuration(file_str); - replace(file_str, old_duration, duration); - string new_str = file_str.substr(0, file_str.length() - 15); - new_str.append(body); - File::saveFile(new_str, _m3u8_file_path.data()); + // 第二次进来,去掉 "#EXT-X-ENDLIST\n",再重新追加file_content,保存文件 + auto file = File::create_file(_m3u8_file_path.data(), "r+"); + if (file) { + fseek(file, -15, SEEK_END); + fwrite(body.data(), body.size(), 1, file); + fclose(file); + } } } diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h index 5c6491b2..38ce3002 100644 --- a/src/Record/HlsMakerSub.h +++ b/src/Record/HlsMakerSub.h @@ -116,7 +116,6 @@ private: //新增函数,实现录像功能 std::string getTsFile(const std::string &file_content); - std::string getM3u8TSDuration(const std::string &file_content); std::string getM3u8TSBody(const std::string &file_content); void createM3u8FileForRecord(); From edd719b68c16d3f2c3a131860827786399d9f55f Mon Sep 17 00:00:00 2001 From: renlu Date: Wed, 28 Sep 2022 08:54:52 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E3=80=81=E4=BF=AE=E6=94=B9=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BB=A5=E5=8F=8A=E5=BD=95=E5=83=8F?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A9=BA=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- default.pem | 106 +++++++++++++++++----------------- server/WebApi.cpp | 67 ++++++++++----------- src/Record/HlsMakerImpSub.cpp | 1 + src/Record/HlsMakerSub.cpp | 6 +- 4 files changed, 93 insertions(+), 87 deletions(-) diff --git a/default.pem b/default.pem index 2cef8d27..97460132 100644 --- a/default.pem +++ b/default.pem @@ -1,37 +1,64 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2 +uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha +0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl +dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76 +juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp +9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X +v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ +6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB +ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8 +TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50 +9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC +16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh +xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh +S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1 +eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr +zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw +sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa +/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa +bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV +KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB +dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t +b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h +APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj +mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ +GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ +-----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIGATCCBOmgAwIBAgIQC+3yGsMpfdf8+/3qyyeKMjANBgkqhkiG9w0BAQsFADBu +MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg -RFYgVExTIENBIC0gRzEwHhcNMjEwOTEzMDAwMDAwWhcNMjIwOTEzMjM1OTU5WjAh +RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAiTtbBLkfqhvnBehH+4hgdaR+b+Y36YLc6+B9Mrcm -3Z400xbLB79xOkpVQ3XDsikJS3oCTwigBwOco9ulTnkB2zKnWo0WeqBgyoI1g+uk -S/Cjmocl4Pq2bhWWTCHmPqhSkikEHXixfUPHzAUhRDSXnhxFnJX2HW+LyR5rnspw -7PkWuhepurIoLq/tVGKnO5OX/2cOmInAPGjrBG+SFa1Z0+D0lCTextP7CKirMQyo -IrUR0ZDwFTREROjwxYTXrUZw3Wrv2JQI+HW/sf71Mp0b0qwFDA8xBvtn9PBrG83d -20GMGoXMqwa8W4gsevczZxq24fC+W8UEX2YIc+MCOwqxJQIDAQABo4IC5jCCAuIw -HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFHnDhdRf -5j6H38/6FDh5dUxSafFeMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j +AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv +nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc +S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw +oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx +0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck +TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw +HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz +q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx -LmNydDAJBgNVHRMEAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdwApeb7w -njk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXvfJJeIAAAEAwBIMEYCIQDn -tznKOZ7m++ulwy19YpiX7nSYbtLK3X2oEOglBNPWeQIhAKtxstW4CPWeqS0skXy3 -Bfl3INTuswwya4V93LRxU11qAHUAUaOw9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN -/tSLBeUAAAF73ySX8wAABAMARjBEAiBJXGMdexhrjMMRBtYl8HATnpr01wV7glwt -RfnU4ymvBQIgP0jIov7IKd3MM54OOzQ6lzrN7kYtw+Dxnk12dMldlJkAdgBByMqx -3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAXvfJJfGAAAEAwBHMEUCIDbB -3Pc+Sr3rZ84ou3AfbTK/1JZ0p2T2MulEIfgftqqqAiEAzsSmKwwiq4QhL4v8Vgci -0Z8cN5lNdaq5ofNpBGWnaD8wDQYJKoZIhvcNAQELBQADggEBAA8NRtfJgzD/gXku -n83SQp1g2ZPDIJYALegUu90fpc9OzJjdFOeaqw0s3futhTaJrwRU8CRITQnMeaj+ -6rHNWxtQp7A3E2/U2R5E374x4T8Z0fJD+WqANNYVnbt0Kw7bOrY8lRizyL5KMbSj -DKjatMhXAvWMS1Bj0LXBQFtnoBQKo83VQdBxKUK5cK3EN//jrT1IUf/g4R0taQ3f -G18oSCc1kE/giaDnZhEjv8R2KLr4fOC9j7paigZHKYEolO4CMq30hADYDZ06IGjU -vNgB0dkmcr2S7xlQ5creU/8tUmUodidcN4/4PCpUV9X7WVGHO5+F8B205/gEydVc -dduKrjk= +LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa +PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO +G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p +wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD +wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5 ++MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH +4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/ +kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t +GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ +FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW +B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj +ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u +vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp +HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc +pPW8w+2c -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh @@ -59,31 +86,4 @@ M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV 4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg== ------END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAiTtbBLkfqhvnBehH+4hgdaR+b+Y36YLc6+B9Mrcm3Z400xbL -B79xOkpVQ3XDsikJS3oCTwigBwOco9ulTnkB2zKnWo0WeqBgyoI1g+ukS/Cjmocl -4Pq2bhWWTCHmPqhSkikEHXixfUPHzAUhRDSXnhxFnJX2HW+LyR5rnspw7PkWuhep -urIoLq/tVGKnO5OX/2cOmInAPGjrBG+SFa1Z0+D0lCTextP7CKirMQyoIrUR0ZDw -FTREROjwxYTXrUZw3Wrv2JQI+HW/sf71Mp0b0qwFDA8xBvtn9PBrG83d20GMGoXM -qwa8W4gsevczZxq24fC+W8UEX2YIc+MCOwqxJQIDAQABAoIBABdmIf5brFz+dfVJ -ZmCqn7vfaNmemQD9Wbr0Y5SOqxdVnu8xLzwqdd15CDHA9jW+DoIqkxMzxsl7Ya2E -yZpoQptD90oWzXLqPqa47fQI7VIvfU3fZmOGjC2YC7D+hLpBTBb03GlEB4tyz7Hn -XAU7rHB+pJXu8fCR8PVBdRs1rGyTSlOV/o2Q9dnnoAX6AIDWi63ChEJCrl0Mo9pL -e1naRN3hPFJ3SzJnPB6AUhGlxxV0oc71fnPpvW11tlpWshbVXIHJ2yS1zQcTvshj -pbOC9KRsbRQPJA+NEGOEciCP9H/Lh0lFle/zzSbpzetwgPlqTuaPbFwknz6xGux4 -GaBLrH8CgYEAvP6lGIX3a2Ot04bYoQPfO3vRUPigFKCPKQgj62B/M2SVayuMmeAF -9WvbWHLP/gx+vg7d3Pmg5YGeFnWSnrOAfjW4758yhA5f3/XDkSsMqc/NT+bsURuS -Z+sDzeOwYp8kznDPaj0k4XhUSej+6vYM+zsbMzqqsGkTQj1Ncv5eohcCgYEAueKp -MPZtYte8ID/g//p9YPCg9FNiuGMMAvUyig1PQrChSwiLflSoDJsbrtD69IliP+4y -hWeD/X8w+DUv34aQQIsSwoci6gEmG8ErqVOYvCjxksgM+2r0cGJVxqaGUB0gdjah -Z/7ND/yByr0qxuz8izaGfDybGo5F0QB13692uCMCgYAaQ/mF0vhzwEKkJxVsKzGW -/ro0WplExJugxDTZvWtwJQZvAnpj2DJ7zSWKwUoOsIXcvAwxba/itYTW8jgSPjgZ -UjYFd0Z5+9VvNqSbRDRaVTrfY+Rr0T0jnBHHR2F4E032Ms9goGbDvwlXzD3BQbjE -IY7CK+EU60V16zccSCW2uQKBgQC2GJYYEgA8aQyxJwK6oN98PJ3gW2OFL/pPV3aI -CNvRgAix4ZANVM8/ch9fVPfS4FbwO98gErUZeyU0sZ3RQhhEMjlReWK5jCCR5d1o -xi0EfrOQUAtvrGoDQkG3FeDT0ITBaWka4GBwPbPEMSYbs4L+uY5rXE+xZxh70xCl -7VTGswKBgQCOrkDjkzgwg1+GvEFmhUi+N/UaYXzOUvx6ciuxPwb+dyMy1jGpsYhE -1eLJuwtpda2aZpvLzM8HC8a7Amkff/kw8n4d0y0iM2xQ9nZSMx1pZXTCToMK2EDJ -b54tt/amcS1qFF7H3CBDYrkHcr+qPL2ypr+onOGkU3MufFXZk7NcVA== ------END RSA PRIVATE KEY----- +-----END CERTIFICATE----- \ No newline at end of file diff --git a/server/WebApi.cpp b/server/WebApi.cpp index ad3b8a27..5d65a593 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -1471,43 +1471,44 @@ void installWebApi() { auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/"; string new_snap = StrPrinter << scan_path << time(NULL) << ".jpeg"; - File::scanDir(scan_path, [&](const string &path, bool isDir) { - if (isDir || !end_with(path, ".jpeg")) { - //忽略文件夹或其他类型的文件 - return true; + if (expire_sec != 0){ + File::scanDir(scan_path, [&](const string &path, bool isDir) { + if (isDir || !end_with(path, ".jpeg")) { + //忽略文件夹或其他类型的文件 + return true; + } + + //找到截图 + auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); + if (atoll(tm.data()) + expire_sec < time(NULL)) { + //截图已经过期,改名,以便再次请求时,可以返回老截图 + rename(path.data(), new_snap.data()); + have_old_snap = true; + return true; + } + + //截图存在,且未过期,那么返回之 + res_old_snap = true; + responseSnap(path, allArgs.getParser().getHeader(), invoker); + //中断遍历 + return false; + }); + + if (res_old_snap) { + //已经回复了旧的截图 + return; } - //找到截图 - auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); - if (atoll(tm.data()) + expire_sec < time(NULL)) { - //截图已经过期,改名,以便再次请求时,可以返回老截图 - rename(path.data(), new_snap.data()); - have_old_snap = true; - return true; - } - - //截图存在,且未过期,那么返回之 - res_old_snap = true; - responseSnap(path, allArgs.getParser().getHeader(), invoker); - //中断遍历 - return false; - }); - - if (res_old_snap) { - //已经回复了旧的截图 - return; - } - - //无截图或者截图已经过期 - if (!have_old_snap) { - //无过期截图,生成一个空文件,目的是顺便创建文件夹路径 - //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程 - auto file = File::create_file(new_snap.data(), "wb"); - if (file) { - fclose(file); + //无截图或者截图已经过期 + if (!have_old_snap) { + //无过期截图,生成一个空文件,目的是顺便创建文件夹路径 + //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程 + auto file = File::create_file(new_snap.data(), "wb"); + if (file) { + fclose(file); + } } } - //启动FFmpeg进程,开始截图,生成临时文件,截图成功后替换为正式文件 auto new_snap_tmp = new_snap + ".tmp"; FFmpegSnap::makeSnap(allArgs["url"], new_snap_tmp, allArgs["timeout_sec"], [invoker, allArgs, new_snap, new_snap_tmp](bool success, const string &err_msg) { diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp index 0f77d366..33a35f2a 100644 --- a/src/Record/HlsMakerImpSub.cpp +++ b/src/Record/HlsMakerImpSub.cpp @@ -101,6 +101,7 @@ string HlsMakerImpSub::onOpenSegment(uint64_t index) { if (!_file) { WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); + return ""; } if (_params.empty()) { return strDate + "/" + strHour + "/" + segment_name; diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index ba114f72..f28f0dca 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -37,7 +37,7 @@ HlsMakerSub::~HlsMakerSub() { } void HlsMakerSub::startRecord(bool isRecord) { - if (_is_record) {//检测到上一次是在录像,清空_segment_file_paths + if (_is_record) { //检测到上一次是在录像,清空_segment_file_paths _segment_file_paths.clear(); } @@ -267,6 +267,10 @@ void HlsMakerSub::createM3u8FileForRecord() { } _m3u8_file_num = 0; + if (_m3u8_file_path.empty()) { + WarnL << "create m3u8 file failed, _m3u8_file_path is empty." ; + return; + } //3.写m3u8文件 string m3u8Header = "#EXTM3U\n" From 31293d9fe3d92ed39d86eb3cf93ee247333ceb33 Mon Sep 17 00:00:00 2001 From: renlu Date: Fri, 30 Sep 2022 13:29:36 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BD=95=E5=83=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=A7=A3=E5=86=B3=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?coredump=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerImpSub.cpp | 6 ++-- src/Record/HlsMakerSub.cpp | 52 ++++++++++++++++++++--------------- src/Record/HlsMakerSub.h | 1 - 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp index 33a35f2a..71ebc1e6 100644 --- a/src/Record/HlsMakerImpSub.cpp +++ b/src/Record/HlsMakerImpSub.cpp @@ -84,9 +84,9 @@ string HlsMakerImpSub::onOpenSegment(uint64_t index) { if (isLive()) { GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); - GET_CONFIG(uint32_t, segKeep, Hls::kSegmentNum); - if (_segment_file_paths.size() > segRetain + segKeep) { - _segment_file_paths.erase(index - segRetain - segKeep -1); + GET_CONFIG(uint32_t, segNum, Hls::kSegmentNum); + if (_segment_file_paths.size() > segRetain + segNum) { + _segment_file_paths.erase(index - segRetain - segNum - 1); } _segment_file_paths.emplace(index, segment_path); } diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index f28f0dca..fb20592f 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -15,6 +15,7 @@ #define _access access #else #include +#include #endif // WIN32 @@ -156,6 +157,7 @@ void HlsMakerSub::addNewSegment(uint64_t stamp) { _last_file_name = onOpenSegment(_file_index++); //记录本次切片的起始时间戳 _last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; + } void HlsMakerSub::flushLastSegment(bool eof) { @@ -178,6 +180,7 @@ void HlsMakerSub::flushLastSegment(bool eof) { if (_is_record) { createM3u8FileForRecord(); } + } bool HlsMakerSub::isLive() { @@ -194,6 +197,7 @@ void HlsMakerSub::clear() { _last_seg_timestamp = 0; _seg_dur_list.clear(); _last_file_name.clear(); + } std::string HlsMakerSub::getM3u8TSBody(const std::string &file_content) { @@ -231,7 +235,7 @@ std::string HlsMakerSub::getTsFile(const std::string &file_content) { } void HlsMakerSub::createM3u8FileForRecord() { - // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长 + // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长,并生成m3u8文件的路径 string live_file = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); if (live_file.empty()) { return; @@ -241,32 +245,35 @@ void HlsMakerSub::createM3u8FileForRecord() { string ts_file_name = getTsFile(live_file); // ts_file: 2022-09-14_11-06-03 string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/"; - // 2.判断该目录下有没有m3u8文件,没有的话,生成第一个m3u8文件,有的话,重命名 - File::scanDir( - m3u8_file, - [this](const string &path, bool isDir) -> bool { - if (!isDir && end_with(path, ".m3u8")) { - _m3u8_file_num++; + int handle = -1; + DIR *dir_info = opendir(m3u8_file.data()); + struct dirent *dir_entry; + if (dir_info) { + while ((dir_entry =readdir(dir_info)) != NULL) { + if (end_with(dir_entry->d_name, ".m3u8")) { + handle = 0; + break; } - return true; - }, - false); - - if (_m3u8_file_num == 0) { //第一次播放流 - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; - _is_close_stream = false; - } else { //断流过,一次以上播放 - if (_is_close_stream) { - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; - _is_close_stream = false; - } - if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空 - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; } + closedir(dir_info); + } else { + return; } - _m3u8_file_num = 0; + if (-1 == handle) {//第一次播放流 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } else {//断流过,一次以上播放 + if (_is_close_stream) { + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } + if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + } + } + if (_m3u8_file_path.empty()) { WarnL << "create m3u8 file failed, _m3u8_file_path is empty." ; return; @@ -278,6 +285,7 @@ void HlsMakerSub::createM3u8FileForRecord() { "#EXT-X-VERSION:4\n" "#EXT-X-TARGETDURATION:2\n" "#EXT-X-MEDIA-SEQUENCE:0\n"; + if (access(_m3u8_file_path.data(), 0) != 0) { //文件不存在 auto file = File::create_file(_m3u8_file_path.data(), "wb"); if (file) { diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h index 38ce3002..f8a244a5 100644 --- a/src/Record/HlsMakerSub.h +++ b/src/Record/HlsMakerSub.h @@ -130,7 +130,6 @@ private: std::deque > _seg_dur_list; bool _is_record = false; bool _is_close_stream = false; - int _m3u8_file_num = 0; std::string _m3u8_file_path; public: From 5364d8a001f1a3ab8a073b1c9957227c66870171 Mon Sep 17 00:00:00 2001 From: renlu Date: Mon, 21 Nov 2022 17:10:36 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=9B=B4=E6=92=AD?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=9A=848=E4=B8=AAts=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E3=80=82=E4=BB=A5=E5=85=8D=E5=A4=9A=E6=AC=A1=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=AF=BC=E8=87=B4ts=E6=96=87=E4=BB=B6=E7=B4=AF=E7=A7=AF?= =?UTF-8?q?=E8=BF=87=E5=A4=9A=E5=8D=A0=E7=94=A8=E5=86=85=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerImpSub.cpp | 37 +++++++++++++++-------------------- src/Record/HlsMakerImpSub.h | 1 - src/Record/HlsMakerSub.cpp | 17 +++++++++++++++- src/Record/HlsMakerSub.h | 3 ++- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp index 71ebc1e6..b5541e78 100644 --- a/src/Record/HlsMakerImpSub.cpp +++ b/src/Record/HlsMakerImpSub.cpp @@ -26,7 +26,6 @@ HlsMakerImpSub::HlsMakerImpSub( float seg_duration, uint32_t seg_number, bool seg_keep):HlsMakerSub(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; @@ -55,22 +54,24 @@ void HlsMakerImpSub::clearCache(bool immediately, bool eof) { clear(); _file = nullptr; + //删除_segment_file_paths路径对应的直播文件 + for (auto it : _segment_file_paths) { + auto ts_path = it.second; + File::delete_file(ts_path.data()); + } _segment_file_paths.clear(); - //删除缓存的m3u8文件 - File::delete_file((_path_prefix + "/hls.m3u8").data() ); + //程序异常退出的情况下,直播的8个ts文件还是无法删除, + //这里先删除掉m3u8文件对应的3个ts文件。还有保留的5个ts文件无法删除,能删除几个是几个吧。 + fstream file(_path_prefix + "/hls.m3u8"); + string data; + while (getline(file,data)) { + string ts_path = _path_prefix + "/" + data; + File::delete_file(ts_path.data()); + } - ////hls直播才删除文件 - //GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); - //if (!delay || immediately) { - // File::delete_file(_path_prefix.data()); - //} else { - // auto path_prefix = _path_prefix; - // _poller->doDelayTask(delay * 1000, [path_prefix]() { - // File::delete_file(path_prefix.data()); - // return 0; - // }); - //} + //删除缓存的m3u8文件 + File::delete_file((_path_prefix + "/hls.m3u8").data()); } string HlsMakerImpSub::onOpenSegment(uint64_t index) { @@ -81,13 +82,7 @@ string HlsMakerImpSub::onOpenSegment(uint64_t index) { auto strTime = getTimeStr("%M-%S"); segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts"; segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name; - if (isLive()) { - - GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); - GET_CONFIG(uint32_t, segNum, Hls::kSegmentNum); - if (_segment_file_paths.size() > segRetain + segNum) { - _segment_file_paths.erase(index - segRetain - segNum - 1); - } + if ((!isKeep())) { _segment_file_paths.emplace(index, segment_path); } diff --git a/src/Record/HlsMakerImpSub.h b/src/Record/HlsMakerImpSub.h index 6a9d7fdd..043937b7 100644 --- a/src/Record/HlsMakerImpSub.h +++ b/src/Record/HlsMakerImpSub.h @@ -71,7 +71,6 @@ private: std::shared_ptr _file; std::shared_ptr _file_buf; HlsMediaSource::Ptr _media_src; - toolkit::EventPoller::Ptr _poller; }; diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index fb20592f..55de73cf 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -30,6 +30,7 @@ HlsMakerSub::HlsMakerSub(float seg_duration, uint32_t seg_number, bool seg_keep) _seg_duration = seg_duration; _seg_keep = seg_keep; _is_record = false; + _poller = EventPollerPool::Instance().getPoller(); } HlsMakerSub::~HlsMakerSub() { @@ -38,8 +39,22 @@ HlsMakerSub::~HlsMakerSub() { } void HlsMakerSub::startRecord(bool isRecord) { - if (_is_record) { //检测到上一次是在录像,清空_segment_file_paths + //本来已经在录像,再次点击录像,或者本来已经停止录像,再次点击停止录像,直接返回 + if (isRecord == _is_record) { + return; + } + //如果是录像,则删除之前直播的8个ts文件 + if (isRecord) { + std::map delete_file_paths = _segment_file_paths; _segment_file_paths.clear(); + //删除_segment_file_paths路径对应的直播文件,过30s再删除,免得hls直播突然断掉 + for (auto it : delete_file_paths) { + auto ts_path = it.second; + _poller->doDelayTask(30 * 1000, [ts_path]() { + File::delete_file(ts_path.data()); + return 0; + }); + } } if(isRecord) { diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h index f8a244a5..3904c358 100644 --- a/src/Record/HlsMakerSub.h +++ b/src/Record/HlsMakerSub.h @@ -18,7 +18,7 @@ #include "Util/File.h" #include "Util/util.h" #include "Util/logger.h" - +#include "ZLToolKit/src/Poller/EventPoller.h" namespace mediakit { class HlsMakerSub { @@ -131,6 +131,7 @@ private: bool _is_record = false; bool _is_close_stream = false; std::string _m3u8_file_path; + toolkit::EventPoller::Ptr _poller; public: std::map _segment_file_paths; From 64ee0bcaf2db284c4572ffc5f757224c01ded6b6 Mon Sep 17 00:00:00 2001 From: renlu Date: Mon, 28 Nov 2022 16:31:00 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9Bug:=20=E4=BB=8E?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E5=88=B0=E5=BD=95=E5=83=8F=EF=BC=8C=E7=BC=BA?= =?UTF-8?q?=E5=B0=91=E7=AC=AC=E4=B8=80=E4=B8=AAts=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerSub.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index 55de73cf..7ebf99b6 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -47,13 +47,17 @@ void HlsMakerSub::startRecord(bool isRecord) { if (isRecord) { std::map delete_file_paths = _segment_file_paths; _segment_file_paths.clear(); + int count = 0; //删除_segment_file_paths路径对应的直播文件,过30s再删除,免得hls直播突然断掉 for (auto it : delete_file_paths) { - auto ts_path = it.second; - _poller->doDelayTask(30 * 1000, [ts_path]() { - File::delete_file(ts_path.data()); - return 0; - }); + count ++; + if (count < delete_file_paths.size()) { + auto ts_path = it.second; + _poller->doDelayTask(30 * 1000, [ts_path]() { + File::delete_file(ts_path.data()); + return 0; + }); + } } } From 99e420d784e04daa8528855d9bb2347a279c19b5 Mon Sep 17 00:00:00 2001 From: renlu Date: Mon, 28 Nov 2022 16:47:21 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9E=90=E6=9E=84?= =?UTF-8?q?=E4=B8=AD=E8=B0=83=E7=94=A8getOwnerPoller=E6=8A=9B=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=AF=BC=E8=87=B4=E5=B4=A9=E6=BA=83=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebApi.cpp | 7 +++++- src/Common/MediaSource.cpp | 48 +++++++++++++++++++++++++++----------- src/Common/MediaSource.h | 2 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index e7d1fa79..535933e9 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -344,7 +344,12 @@ Value makeMediaSourceJson(MediaSource &media){ } //getLossRate有线程安全问题;使用getMediaInfo接口才能获取丢包率;getMediaList接口将忽略丢包率 - auto current_thread = media.getOwnerPoller()->isCurrentThread(); + //auto current_thread = media.getOwnerPoller()->isCurrentThread(); + auto current_thread = false; + try { + current_thread = media.getOwnerPoller()->isCurrentThread(); + } catch (...) { + } float last_loss = -1; for(auto &track : media.getTracks(false)){ Value obj; diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index bcd16cb9..149b1a3e 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -124,7 +124,7 @@ MediaSource::MediaSource(const string &schema, const string &vhost, const string _app = app; _stream_id = stream_id; _create_stamp = time(NULL); - _default_poller = EventPollerPool::Instance().getPoller(); + //_default_poller = EventPollerPool::Instance().getPoller(); } MediaSource::~MediaSource() { @@ -289,23 +289,43 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() { if (listener) { return listener->getOwnerPoller(*this); } - WarnL << toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed, now return default poller: " + getUrl(); - return _default_poller; + // WarnL << toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed, now return default poller: " + getUrl(); + //return _default_poller; + throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl()); } void MediaSource::onReaderChanged(int size) { - weak_ptr weak_self = shared_from_this(); - auto listener = _listener.lock(); - if (!listener) { - return; + //weak_ptr weak_self = shared_from_this(); + //auto listener = _listener.lock(); + //if (!listener) { + // return; + //} + //getOwnerPoller()->async([weak_self, size, listener]() { + // auto strong_self = weak_self.lock(); + // if (!strong_self) { + // return; + // } + // listener->onReaderChanged(*strong_self, size); + //}); + try { + weak_ptr weak_self = shared_from_this(); + getOwnerPoller()->async([weak_self, size]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + auto listener = strong_self->_listener.lock(); + if (listener) { + listener->onReaderChanged(*strong_self, size); + } + }); + } catch (MediaSourceEvent::NotImplemented &ex) { + // 未实现接口,应该打印异常 + WarnL << ex.what(); + } catch (...) { + // getOwnerPoller()接口抛异常机制应该只对外不对内 + // 所以listener已经销毁导致获取归属线程失败的异常直接忽略 } - getOwnerPoller()->async([weak_self, size, listener]() { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return; - } - listener->onReaderChanged(*strong_self, size); - }); } bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path, size_t max_second){ diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index 698cefca..2551ea48 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -406,7 +406,7 @@ private: std::string _app; std::string _stream_id; std::weak_ptr _listener; - toolkit::EventPoller::Ptr _default_poller; + //toolkit::EventPoller::Ptr _default_poller; // 对象个数统计 toolkit::ObjectStatistic _statistic; }; From 7801467e3602f90785dbc9a7edfbf07608735011 Mon Sep 17 00:00:00 2001 From: renlu Date: Fri, 2 Dec 2022 11:01:15 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=90=84=E4=BD=8D=E4=B8=BAUFT-8=20BOM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/WebApi.cpp | 69 +++++++++++++++++++------------------- src/Common/MediaSource.cpp | 2 +- src/Common/MediaSource.h | 2 +- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 61887a65..319a222d 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). @@ -1485,44 +1485,43 @@ void installWebApi() { auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/"; string new_snap = StrPrinter << scan_path << time(NULL) << ".jpeg"; - if (expire_sec != 0){ - File::scanDir(scan_path, [&](const string &path, bool isDir) { - if (isDir || !end_with(path, ".jpeg")) { - //忽略文件夹或其他类型的文件 - return true; - } - - //找到截图 - auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); - if (atoll(tm.data()) + expire_sec < time(NULL)) { - //截图已经过期,改名,以便再次请求时,可以返回老截图 - rename(path.data(), new_snap.data()); - have_old_snap = true; - return true; - } - - //截图存在,且未过期,那么返回之 - res_old_snap = true; - responseSnap(path, allArgs.getParser().getHeader(), invoker); - //中断遍历 - return false; - }); - - if (res_old_snap) { - //已经回复了旧的截图 - return; + File::scanDir(scan_path, [&](const string &path, bool isDir) { + if (isDir || !end_with(path, ".jpeg")) { + //忽略文件夹或其他类型的文件 + return true; } - //无截图或者截图已经过期 - if (!have_old_snap) { - //无过期截图,生成一个空文件,目的是顺便创建文件夹路径 - //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程 - auto file = File::create_file(new_snap.data(), "wb"); - if (file) { - fclose(file); - } + //找到截图 + auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); + if (atoll(tm.data()) + expire_sec < time(NULL)) { + //截图已经过期,改名,以便再次请求时,可以返回老截图 + rename(path.data(), new_snap.data()); + have_old_snap = true; + return true; + } + + //截图存在,且未过期,那么返回之 + res_old_snap = true; + responseSnap(path, allArgs.getParser().getHeader(), invoker); + //中断遍历 + return false; + }); + + if (res_old_snap) { + //已经回复了旧的截图 + return; + } + + //无截图或者截图已经过期 + if (!have_old_snap) { + //无过期截图,生成一个空文件,目的是顺便创建文件夹路径 + //同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程 + auto file = File::create_file(new_snap.data(), "wb"); + if (file) { + fclose(file); } } + //启动FFmpeg进程,开始截图,生成临时文件,截图成功后替换为正式文件 auto new_snap_tmp = new_snap + ".tmp"; FFmpegSnap::makeSnap(allArgs["url"], new_snap_tmp, allArgs["timeout_sec"], [invoker, allArgs, new_snap, new_snap_tmp](bool success, const string &err_msg) { diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 1144f110..5e378124 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index f8510538..84961d2c 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). From 696e28765b28ad7c4ae76a148ea80a91958642ae Mon Sep 17 00:00:00 2001 From: renlu Date: Tue, 6 Dec 2022 14:26:57 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B9=20=E9=9D=9E=E5=BD=95=E5=83=8F=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E6=97=A0=E4=BA=BA=E8=A7=82=E7=9C=8B=E5=BB=B6?= =?UTF-8?q?=E6=97=B6=E6=97=B6=E9=95=BF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.ini | 1 + src/Common/MediaSource.cpp | 73 +++++++++++++++++++++++++++----------- src/Common/config.cpp | 2 ++ src/Common/config.h | 1 + 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index ae0ce077..2fe23ad0 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -94,6 +94,7 @@ maxStreamWaitMS=15000 #某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 #在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 streamNoneReaderDelayMS=20000 +noRecordStreamNoneReaderDelayMS=2000 #拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, #如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) resetWhenRePlay=1 diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 5e378124..83507e35 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -662,32 +662,65 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ //没有任何人观看该视频源,表明该源可以关闭了 GET_CONFIG(string, record_app, Record::kAppName); GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS); + GET_CONFIG(int, no_record_stream_none_reader_delay, General::kNoRecordStreamNoneReaderDelayMS); //如果mp4点播, 无人观看时我们强制关闭点播 bool is_mp4_vod = sender.getApp() == record_app; weak_ptr weak_sender = sender.shared_from_this(); + + if(sender.isRecording(Recorder::type_hls)) {//如果正在录像 + _async_close_timer = std::make_shared( + stream_none_reader_delay / 1000.0f, + [weak_sender, is_mp4_vod]() { + auto strong_sender = weak_sender.lock(); + if (!strong_sender) { + //对象已经销毁 + return false; + } - _async_close_timer = std::make_shared(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { - auto strong_sender = weak_sender.lock(); - if (!strong_sender) { - //对象已经销毁 - return false; - } + if (strong_sender->totalReaderCount()) { + //还有人观看该视频,不触发关闭事件 + return false; + } - if (strong_sender->totalReaderCount()) { - //还有人观看该视频,不触发关闭事件 - return false; - } + if (!is_mp4_vod) { + //直播时触发无人观看事件,让开发者自行选择是否关闭 + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + } else { + //这个是mp4点播,我们自动关闭 + WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); + strong_sender->close(false); + } + return false; + }, + nullptr); + } else {//没有录像的话 + _async_close_timer = std::make_shared( + no_record_stream_none_reader_delay / 1000.0f, + [weak_sender, is_mp4_vod]() { + auto strong_sender = weak_sender.lock(); + if (!strong_sender) { + //对象已经销毁 + return false; + } - if (!is_mp4_vod) { - //直播时触发无人观看事件,让开发者自行选择是否关闭 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); - } else { - //这个是mp4点播,我们自动关闭 - WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); - strong_sender->close(false); - } - return false; - }, nullptr); + if (strong_sender->totalReaderCount()) { + //还有人观看该视频,不触发关闭事件 + return false; + } + + if (!is_mp4_vod) { + //直播时触发无人观看事件,让开发者自行选择是否关闭 + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + } else { + //这个是mp4点播,我们自动关闭 + WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); + strong_sender->close(false); + } + return false; + }, + nullptr); + } + } string MediaSourceEvent::getOriginUrl(MediaSource &sender) const { diff --git a/src/Common/config.cpp b/src/Common/config.cpp index cb24064b..e6611f1e 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -66,6 +66,7 @@ namespace General { const string kMediaServerId = GENERAL_FIELD "mediaServerId"; const string kFlowThreshold = GENERAL_FIELD "flowThreshold"; const string kStreamNoneReaderDelayMS = GENERAL_FIELD "streamNoneReaderDelayMS"; +const string kNoRecordStreamNoneReaderDelayMS = GENERAL_FIELD "noRecordStreamNoneReaderDelayMS"; const string kMaxStreamWaitTimeMS = GENERAL_FIELD "maxStreamWaitMS"; const string kEnableVhost = GENERAL_FIELD "enableVhost"; const string kResetWhenRePlay = GENERAL_FIELD "resetWhenRePlay"; @@ -79,6 +80,7 @@ const string kUnreadyFrameCache = GENERAL_FIELD "unready_frame_cache"; static onceToken token([]() { mINI::Instance()[kFlowThreshold] = 1024; mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000; + mINI::Instance()[kNoRecordStreamNoneReaderDelayMS] = 20 * 1000; mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000; mINI::Instance()[kEnableVhost] = 0; mINI::Instance()[kResetWhenRePlay] = 1; diff --git a/src/Common/config.h b/src/Common/config.h index 36177ec4..260178fd 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -158,6 +158,7 @@ extern const std::string kFlowThreshold; // 流无人观看并且超过若干时间后才触发kBroadcastStreamNoneReader事件 // 默认连续5秒无人观看然后触发kBroadcastStreamNoneReader事件 extern const std::string kStreamNoneReaderDelayMS; +extern const std::string kNoRecordStreamNoneReaderDelayMS; // 等待流注册超时时间,收到播放器后请求后,如果未找到相关流,服务器会等待一定时间, // 如果在这个时间内,相关流注册上了,那么服务器会立即响应播放器播放成功, // 否则会最多等待kMaxStreamWaitTimeMS毫秒,然后响应播放器播放失败 From 0d40bf9272cdc1cf82b249d5591e1dc016b51c1c Mon Sep 17 00:00:00 2001 From: renlu Date: Wed, 7 Dec 2022 08:52:01 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=BB=B6=E6=97=B630s?= =?UTF-8?q?=E5=86=8D=E5=88=A0=E9=99=A4=E7=9B=B4=E6=92=AD=E7=9A=84ts?= =?UTF-8?q?=E7=9A=84=E6=93=8D=E4=BD=9C=E3=80=82=E4=BB=8E=E7=9B=B4=E6=92=AD?= =?UTF-8?q?=E5=88=B0=E5=BD=95=E5=83=8F=E7=9B=B4=E6=8E=A5=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E7=9A=848=E4=B8=AAts=E5=88=87=E7=89=87?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerSub.cpp | 11 ++++++----- src/Record/HlsMakerSub.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index 7ebf99b6..361aeb33 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -30,7 +30,7 @@ HlsMakerSub::HlsMakerSub(float seg_duration, uint32_t seg_number, bool seg_keep) _seg_duration = seg_duration; _seg_keep = seg_keep; _is_record = false; - _poller = EventPollerPool::Instance().getPoller(); + //_poller = EventPollerPool::Instance().getPoller(); } HlsMakerSub::~HlsMakerSub() { @@ -53,10 +53,11 @@ void HlsMakerSub::startRecord(bool isRecord) { count ++; if (count < delete_file_paths.size()) { auto ts_path = it.second; - _poller->doDelayTask(30 * 1000, [ts_path]() { - File::delete_file(ts_path.data()); - return 0; - }); + File::delete_file(ts_path.data()); + //_poller->doDelayTask(30 * 1000, [ts_path]() { + // File::delete_file(ts_path.data()); + // return 0; + //}); } } } diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h index 3904c358..622f1b5b 100644 --- a/src/Record/HlsMakerSub.h +++ b/src/Record/HlsMakerSub.h @@ -131,7 +131,7 @@ private: bool _is_record = false; bool _is_close_stream = false; std::string _m3u8_file_path; - toolkit::EventPoller::Ptr _poller; + //toolkit::EventPoller::Ptr _poller; public: std::map _segment_file_paths; From c28e96c3afeff711606eb80d7bd7cd6082169ab5 Mon Sep 17 00:00:00 2001 From: renlu Date: Thu, 8 Dec 2022 09:47:33 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E4=BB=8E=E7=9B=B4=E6=92=AD=E5=88=B0?= =?UTF-8?q?=E5=BD=95=E5=83=8F=EF=BC=8C=E4=B8=8D=E5=88=A0=E9=99=A4=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E7=9A=848=E4=B8=AA=E5=88=87=E7=89=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerSub.cpp | 36 ++++++++++++++++++------------------ src/Record/HlsMakerSub.h | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp index 361aeb33..84a033ae 100644 --- a/src/Record/HlsMakerSub.cpp +++ b/src/Record/HlsMakerSub.cpp @@ -43,24 +43,24 @@ void HlsMakerSub::startRecord(bool isRecord) { if (isRecord == _is_record) { return; } - //如果是录像,则删除之前直播的8个ts文件 - if (isRecord) { - std::map delete_file_paths = _segment_file_paths; - _segment_file_paths.clear(); - int count = 0; - //删除_segment_file_paths路径对应的直播文件,过30s再删除,免得hls直播突然断掉 - for (auto it : delete_file_paths) { - count ++; - if (count < delete_file_paths.size()) { - auto ts_path = it.second; - File::delete_file(ts_path.data()); - //_poller->doDelayTask(30 * 1000, [ts_path]() { - // File::delete_file(ts_path.data()); - // return 0; - //}); - } - } - } + ////如果是录像,则删除之前直播的8个ts文件 + //if (isRecord) { + // std::map delete_file_paths = _segment_file_paths; + // _segment_file_paths.clear(); + // int count = 0; + // //删除_segment_file_paths路径对应的直播文件,过10s再删除,免得hls直播突然断掉 + // for (auto it : delete_file_paths) { + // count ++; + // if (count < delete_file_paths.size()) { + // auto ts_path = it.second; + // File::delete_file(ts_path.data()); + // _poller->doDelayTask(10 * 1000, [ts_path]() { + // File::delete_file(ts_path.data()); + // return 0; + // }); + // } + // } + //} if(isRecord) { _seg_keep = true; diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h index 622f1b5b..330447c4 100644 --- a/src/Record/HlsMakerSub.h +++ b/src/Record/HlsMakerSub.h @@ -18,7 +18,7 @@ #include "Util/File.h" #include "Util/util.h" #include "Util/logger.h" -#include "ZLToolKit/src/Poller/EventPoller.h" +//#include "ZLToolKit/src/Poller/EventPoller.h" namespace mediakit { class HlsMakerSub { From 7c05c474e9b011a2bb52a28c2c66ba0dbfeae91b Mon Sep 17 00:00:00 2001 From: renlu Date: Thu, 8 Dec 2022 10:21:47 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E8=AF=BBm3u8=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=B9=B6=E5=88=A0=E9=99=A4=E7=9B=B4=E6=92=AD?= =?UTF-8?q?=E7=9A=84=E4=B8=89=E4=B8=AAts=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMakerImpSub.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp index b5541e78..3842ca30 100644 --- a/src/Record/HlsMakerImpSub.cpp +++ b/src/Record/HlsMakerImpSub.cpp @@ -63,12 +63,13 @@ void HlsMakerImpSub::clearCache(bool immediately, bool eof) { //程序异常退出的情况下,直播的8个ts文件还是无法删除, //这里先删除掉m3u8文件对应的3个ts文件。还有保留的5个ts文件无法删除,能删除几个是几个吧。 - fstream file(_path_prefix + "/hls.m3u8"); - string data; - while (getline(file,data)) { - string ts_path = _path_prefix + "/" + data; - File::delete_file(ts_path.data()); - } + //fstream file(_path_prefix + "/hls.m3u8"); + //string data; + //while (getline(file,data)) { + // string ts_path = _path_prefix + "/" + data; + // File::delete_file(ts_path.data()); + //} + //file.close(); //删除缓存的m3u8文件 File::delete_file((_path_prefix + "/hls.m3u8").data()); From fe22ed258aafe2a7681c8e478f0249b488d87d46 Mon Sep 17 00:00:00 2001 From: renlu Date: Thu, 8 Dec 2022 16:53:42 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E6=AD=A3=E5=9C=A8=E5=BD=95=E5=83=8F=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/MediaSource.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 83507e35..70192d2f 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -668,6 +668,8 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ weak_ptr weak_sender = sender.shared_from_this(); if(sender.isRecording(Recorder::type_hls)) {//如果正在录像 + WarnL << "************The stream is Recording.*************"; + WarnL << sender.getUrl(); _async_close_timer = std::make_shared( stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { @@ -694,6 +696,8 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ }, nullptr); } else {//没有录像的话 + WarnL << "************The stream is Not Recording.*************"; + WarnL << sender.getUrl(); _async_close_timer = std::make_shared( no_record_stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { From 272b65bb4e9fd1afe82e61ff857da9601a135d15 Mon Sep 17 00:00:00 2001 From: renlu Date: Fri, 9 Dec 2022 11:24:17 +0800 Subject: [PATCH 14/16] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E8=A7=82=E7=9C=8B?= =?UTF-8?q?=E6=96=AD=E6=B5=81=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E7=BB=A7?= =?UTF-8?q?=E7=BB=AD=E5=88=A4=E6=96=AD=E6=98=AF=E5=90=A6=E5=9C=A8=E5=BD=95?= =?UTF-8?q?=E5=83=8F=EF=BC=8C=E5=A6=82=E6=9E=9C=E5=9C=A8=E5=BD=95=E5=83=8F?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E6=96=AD=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/MediaSource.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 70192d2f..3bfe6eac 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -666,7 +666,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ //如果mp4点播, 无人观看时我们强制关闭点播 bool is_mp4_vod = sender.getApp() == record_app; weak_ptr weak_sender = sender.shared_from_this(); - + if(sender.isRecording(Recorder::type_hls)) {//如果正在录像 WarnL << "************The stream is Recording.*************"; WarnL << sender.getUrl(); @@ -702,6 +702,10 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ no_record_stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { auto strong_sender = weak_sender.lock(); + + if (strong_sender->isRecording(Recorder::type_hls)) { + return false; + } if (!strong_sender) { //对象已经销毁 return false; From 60ef4f50c89dcbc319f2b8ab2937a10f1295b713 Mon Sep 17 00:00:00 2001 From: renlu Date: Fri, 16 Dec 2022 10:12:36 +0800 Subject: [PATCH 15/16] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8D=E5=BD=95?= =?UTF-8?q?=E5=83=8F=E7=9A=84=E8=AE=BE=E5=A4=87=E7=9A=84=E6=97=A0=E4=BA=BA?= =?UTF-8?q?=E8=A7=82=E7=9C=8B=E6=97=B6=E9=95=BF=E4=B8=BA130=E7=A7=92?= =?UTF-8?q?=EF=BC=8C=E4=BB=A5=E5=90=8E=E5=B0=B1=E4=B8=8D=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=94=B9=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.ini | 2 +- src/Common/config.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/config.ini b/conf/config.ini index 2fe23ad0..fda77b7b 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -94,7 +94,7 @@ maxStreamWaitMS=15000 #某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 #在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 streamNoneReaderDelayMS=20000 -noRecordStreamNoneReaderDelayMS=2000 +noRecordStreamNoneReaderDelayMS=130000 #拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, #如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) resetWhenRePlay=1 diff --git a/src/Common/config.cpp b/src/Common/config.cpp index e6611f1e..30ba5cca 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -80,7 +80,7 @@ const string kUnreadyFrameCache = GENERAL_FIELD "unready_frame_cache"; static onceToken token([]() { mINI::Instance()[kFlowThreshold] = 1024; mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000; - mINI::Instance()[kNoRecordStreamNoneReaderDelayMS] = 20 * 1000; + mINI::Instance()[kNoRecordStreamNoneReaderDelayMS] = 130 * 1000; mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000; mINI::Instance()[kEnableVhost] = 0; mINI::Instance()[kResetWhenRePlay] = 1; From 82af21f910595cfd7dedbedf5f47434cc11946a5 Mon Sep 17 00:00:00 2001 From: renlu Date: Wed, 28 Dec 2022 11:02:02 +0800 Subject: [PATCH 16/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0hls=E5=BD=95=E5=83=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Record/HlsMaker.cpp | 477 ++++++++++++++++++++++------------ src/Record/HlsMaker.h | 262 ++++++++++--------- src/Record/HlsMakerImp.cpp | 339 ++++++++++++------------ src/Record/HlsMakerImp.h | 155 +++++------ src/Record/HlsMakerImpSub.cpp | 180 ------------- src/Record/HlsMakerImpSub.h | 78 ------ src/Record/HlsMakerSub.cpp | 327 ----------------------- src/Record/HlsMakerSub.h | 141 ---------- src/Record/HlsRecorder.h | 16 +- 9 files changed, 702 insertions(+), 1273 deletions(-) delete mode 100644 src/Record/HlsMakerImpSub.cpp delete mode 100644 src/Record/HlsMakerImpSub.h delete mode 100644 src/Record/HlsMakerSub.cpp delete mode 100644 src/Record/HlsMakerSub.h diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index df4cb8de..cbc25753 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -1,169 +1,308 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "HlsMaker.h" -#include "Common/config.h" - -using namespace std; - -namespace mediakit { - -HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { - //最小允许设置为0,0个切片代表点播 - _seg_number = seg_number; - _seg_duration = seg_duration; - _seg_keep = seg_keep; -} - -HlsMaker::~HlsMaker() { -} - - -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 sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); - } 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); - } - - m3u8.assign(file_content); - - 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); - } - - if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); - } - onWriteHls(m3u8); -} - - -void HlsMaker::inputData(void *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; - _last_seg_timestamp = _last_timestamp = timestamp; - } - if (is_idr_fast_packet) { - //尝试切片ts - addNewSegment(timestamp); - } - if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); - _last_timestamp = timestamp; - } - } else { - //resetTracks时触发此逻辑 - flushLastSegment(false); - } -} - -void HlsMaker::delOldSegment() { - if (_seg_number == 0) { - //如果设置为保留0个切片,则认为是保存为点播 - return; - } - //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 - if (_file_index > _seg_number) { - _seg_dur_list.pop_front(); - } - //如果设置为一直保存,就不删除 - if (_seg_keep) { - return; - } - GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); - //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 - if (_file_index > _seg_number + segRetain) { - onDelSegment(_file_index - _seg_number - segRetain - 1); - } -} - -void HlsMaker::addNewSegment(uint64_t stamp) { - if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) { - //存在上个切片,并且未到分片时间 - return; - } - - //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 - flushLastSegment(false); - //新增切片 - _last_file_name = onOpenSegment(_file_index++); - //记录本次切片的起始时间戳 - _last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; -} - -void HlsMaker::flushLastSegment(bool eof){ - if (_last_file_name.empty()) { - //不存在上个切片 - return; - } - //文件创建到最后一次数据写入的时间即为切片长度 - auto seg_dur = _last_timestamp - _last_seg_timestamp; - if (seg_dur <= 0) { - seg_dur = 100; - } - _seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name)); - delOldSegment(); - //先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 - onFlushLastSegment(seg_dur); - //然后写m3u8文件 - makeIndexFile(eof); -} - -bool HlsMaker::isLive() { - return _seg_number != 0; -} - -bool HlsMaker::isKeep() { - return _seg_keep; -} - -void HlsMaker::clear() { - _file_index = 0; - _last_timestamp = 0; - _last_seg_timestamp = 0; - _seg_dur_list.clear(); - _last_file_name.clear(); -} - -}//namespace mediakit +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + + #include "Util/File.h" +#include "HlsMaker.h" +#if defined(_WIN32) +#include +#define _access access +#else +#include +#include +#endif // WIN32 + + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { + //最小允许设置为0,0个切片代表点播 + _seg_number = seg_number; + _seg_duration = seg_duration; + _seg_keep = seg_keep; + _is_record = false; +} + +HlsMaker::~HlsMaker() { + + _is_close_stream = true; +} + +void HlsMaker::startRecord(bool isRecord) { + //本来已经在录像,再次点击录像,或者本来已经停止录像,再次点击停止录像,直接返回 + if (isRecord == _is_record) { + return; + } + + if(isRecord) { + _seg_keep = true; + }else{ + _seg_keep = false; + _is_close_stream = true; + } + _is_record = isRecord; +} + + +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 sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; + + string m3u8; + if (_seg_number == 0) { + // 录像点播支持时移 + snprintf(file_content, sizeof(file_content), + "#EXTM3U\n" + "#EXT-X-PLAYLIST-TYPE:EVENT\n" + "#EXT-X-VERSION:4\n" + "#EXT-X-TARGETDURATION:%u\n" + "#EXT-X-MEDIA-SEQUENCE:%llu\n", + (maxSegmentDuration + 999) / 1000, + sequence); + } 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); + } + + m3u8.assign(file_content); + + 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); + } + + if (eof) { + snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); + m3u8.append(file_content); + } + onWriteHls(m3u8); +} + + +void HlsMaker::inputData(void *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; + _last_seg_timestamp = _last_timestamp = timestamp; + } + if (is_idr_fast_packet) { + //尝试切片ts + addNewSegment(timestamp); + } + if (!_last_file_name.empty()) { + //存在切片才写入ts数据 + onWriteSegment((char *) data, len); + _last_timestamp = timestamp; + } + } else { + //resetTracks时触发此逻辑 + flushLastSegment(false); + } +} + +void HlsMaker::delOldSegment() { + if (_seg_number == 0) { + //如果设置为保留0个切片,则认为是保存为点播 + return; + } + //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 + if (_file_index > _seg_number) { + _seg_dur_list.pop_front(); + } + //如果设置为一直保存,就不删除 + if (_seg_keep) { + return; + } + GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); + //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 + if (_file_index > _seg_number + segRetain) { + onDelSegment(_file_index - _seg_number - segRetain - 1); + } +} + +void HlsMaker::addNewSegment(uint64_t stamp) { + if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) { + //存在上个切片,并且未到分片时间 + return; + } + + //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 + flushLastSegment(false); + + //新增切片 + _last_file_name = onOpenSegment(_file_index++); + //记录本次切片的起始时间戳 + _last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; + +} + +void HlsMaker::flushLastSegment(bool eof) { + if (_last_file_name.empty()) { + //不存在上个切片 + return; + } + //文件创建到最后一次数据写入的时间即为切片长度 + auto seg_dur = _last_timestamp - _last_seg_timestamp; + if (seg_dur <= 0) { + seg_dur = 100; + } + _seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name)); + delOldSegment(); + //先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 + onFlushLastSegment(seg_dur); + //然后写m3u8文件 + makeIndexFile(eof); + //判断当前是否在录像,正在录像的话,生成录像的m3u8文件 + if (_is_record) { + createM3u8FileForRecord(); + } + +} + +bool HlsMaker::isLive() { + return _seg_number != 0; +} + +bool HlsMaker::isKeep() { + return _seg_keep; +} + +void HlsMaker::clear() { + _file_index = 0; + _last_timestamp = 0; + _last_seg_timestamp = 0; + _seg_dur_list.clear(); + _last_file_name.clear(); + +} + +std::string HlsMaker::getM3u8TSBody(const std::string &file_content) { + + string new_file = file_content; + if (file_content.find("#EXT-X-ENDLIST") != file_content.npos) { + //找到了,则去掉"#EXT-X-ENDLIST" + new_file = file_content.substr(0, file_content.length() - 15); + } + + string body = new_file.substr(new_file.find_last_of("#")); + //此时的body为 + //#EXTINF:4.534, + //2022-09-14/08/2022-09-14_08-35-16.ts + string extinf = body.substr(0, body.find(",")+2); + string tsFile = body.substr(body.find_last_of("/") + 1); + body.append("#EXT-X-ENDLIST\n"); + + return extinf + tsFile + "#EXT-X-ENDLIST\n"; +} + +std::string HlsMaker::getTsFile(const std::string &file_content) { + // 最后一个TS的body为 + // 2022-09-13/13/58-13_43.ts + // #EXT-X-ENDLIST + string body = file_content.substr(file_content.find_last_of(",") + 2); + string ts_file_name = body.substr(body.find_last_of("/") + 1); + if (ts_file_name.find("#EXT-X-ENDLIST") == ts_file_name.npos ) { + ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 4); //没找到,去掉“.ts\n”,只留名字 + } else { + ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 19); //找到的话,去掉“.ts\n#EXT-X-ENDLIST\n”,只留名字 + } + + return ts_file_name; +} + +void HlsMaker::createM3u8FileForRecord() { + // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长,并生成m3u8文件的路径 + string live_file = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); + if (live_file.empty()) { + return; + } + + string body = getM3u8TSBody(live_file); + string ts_file_name = getTsFile(live_file); // ts_file: 2022-09-14_11-06-03 + string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/"; + + // 2.判断该目录下有没有m3u8文件,没有的话,生成第一个m3u8文件,有的话,重命名 + int handle = -1; + DIR *dir_info = opendir(m3u8_file.data()); + struct dirent *dir_entry; + if (dir_info) { + while ((dir_entry =readdir(dir_info)) != NULL) { + if (end_with(dir_entry->d_name, ".m3u8")) { + handle = 0; + break; + } + } + closedir(dir_info); + } else { + return; + } + + if (-1 == handle) {//第一次播放流 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } else {//断流过,一次以上播放 + if (_is_close_stream) { + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + _is_close_stream = false; + } + if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空 + _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; + } + } + + if (_m3u8_file_path.empty()) { + WarnL << "create m3u8 file failed, _m3u8_file_path is empty." ; + return; + } + + //3.写m3u8文件 + string m3u8Header = "#EXTM3U\n" + "#EXT-X-PLAYLIST-TYPE:EVENT\n" + "#EXT-X-VERSION:4\n" + "#EXT-X-TARGETDURATION:2\n" + "#EXT-X-MEDIA-SEQUENCE:0\n"; + + if (access(_m3u8_file_path.data(), 0) != 0) { //文件不存在 + auto file = File::create_file(_m3u8_file_path.data(), "wb"); + if (file) { + fwrite(m3u8Header.data(), m3u8Header.size(), 1, file); + fwrite(body.data(), body.size(), 1, file); + fclose(file); + } + } else { + // 第二次进来,去掉 "#EXT-X-ENDLIST\n",再重新追加file_content,保存文件 + auto file = File::create_file(_m3u8_file_path.data(), "r+"); + if (file) { + fseek(file, -15, SEEK_END); + fwrite(body.data(), body.size(), 1, file); + fclose(file); + } + } +} + +}//namespace mediakit diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index 57a5308a..bf45c0dd 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -1,122 +1,140 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef HLSMAKER_H -#define HLSMAKER_H - -#include -#include -#include - -namespace mediakit { - -class HlsMaker { -public: - /** - * @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(); - - /** - * 写入ts数据 - * @param data 数据 - * @param len 数据长度 - * @param timestamp 毫秒时间戳 - * @param is_idr_fast_packet 是否为关键帧第一个包 - */ - void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); - - /** - * 是否为直播 - */ - bool isLive(); - - /** - * 是否保留切片文件 - */ - bool isKeep(); - - /** - * 清空记录 - */ - void clear(); - -protected: - /** - * 创建ts切片文件回调 - * @param index - * @return - */ - virtual std::string onOpenSegment(uint64_t index) = 0; - - /** - * 删除ts切片文件回调 - * @param index - */ - virtual void onDelSegment(uint64_t index) = 0; - - /** - * 写ts切片文件回调 - * @param data - * @param len - */ - virtual void onWriteSegment(const char *data, size_t len) = 0; - - /** - * 写m3u8文件回调 - */ - virtual void onWriteHls(const std::string &data) = 0; - - /** - * 上一个 ts 切片写入完成, 可在这里进行通知处理 - * @param duration_ms 上一个 ts 切片的时长, 单位为毫秒 - */ - virtual void onFlushLastSegment(uint64_t duration_ms) {}; - - /** - * 关闭上个ts切片并且写入m3u8索引 - * @param eof HLS直播是否已结束 - */ - void flushLastSegment(bool eof); - -private: - /** - * 生成m3u8文件 - * @param eof true代表点播 - */ - void makeIndexFile(bool eof = false); - - /** - * 删除旧的ts切片 - */ - void delOldSegment(); - - /** - * 添加新的ts切片 - * @param timestamp - */ - void addNewSegment(uint64_t timestamp); - -private: - float _seg_duration = 0; - uint32_t _seg_number = 0; - bool _seg_keep = false; - uint64_t _last_timestamp = 0; - uint64_t _last_seg_timestamp = 0; - uint64_t _file_index = 0; - std::string _last_file_name; - std::deque > _seg_dur_list; -}; - -}//namespace mediakit -#endif //HLSMAKER_H +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HLSMAKERSUB_H +#define HLSMAKERSUB_H + +#include +#include +#include "Common/config.h" +#include "Util/TimeTicker.h" +#include "Util/File.h" +#include "Util/util.h" +#include "Util/logger.h" + +namespace mediakit { + +class HlsMaker { +public: + /** + * @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(); + + /** + * 写入ts数据 + * @param data 数据 + * @param len 数据长度 + * @param timestamp 毫秒时间戳 + * @param is_idr_fast_packet 是否为关键帧第一个包 + */ + void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + + /** + * 是否为直播 + */ + bool isLive(); + + /** + * 是否保留切片文件 + */ + bool isKeep(); + + /** + * 清空记录 + */ + void clear(); + //设置是否录像标志 + void startRecord(bool isRecord); + +protected: + /** + * 创建ts切片文件回调 + * @param index + * @return + */ + virtual std::string onOpenSegment(uint64_t index) = 0; + + /** + * 删除ts切片文件回调 + * @param index + */ + virtual void onDelSegment(uint64_t index) = 0; + + /** + * 写ts切片文件回调 + * @param data + * @param len + */ + virtual void onWriteSegment(const char *data, size_t len) = 0; + + /** + * 写m3u8文件回调 + */ + virtual void onWriteHls(const std::string &data) = 0; + + /** + * 上一个 ts 切片写入完成, 可在这里进行通知处理 + * @param duration_ms 上一个 ts 切片的时长, 单位为毫秒 + */ + virtual void onFlushLastSegment(uint64_t duration_ms) {}; + virtual std::string getPathPrefix() = 0; + + /** + * 关闭上个ts切片并且写入m3u8索引 + * @param eof HLS直播是否已结束 + */ + void flushLastSegment(bool eof); + +private: + /** + * 生成m3u8文件 + * @param eof true代表点播 + */ + void makeIndexFile(bool eof = false); + + /** + * 删除旧的ts切片 + */ + void delOldSegment(); + + /** + * 添加新的ts切片 + * @param timestamp + */ + void addNewSegment(uint64_t timestamp); + + //新增函数,实现录像功能 + std::string getTsFile(const std::string &file_content); + std::string getM3u8TSBody(const std::string &file_content); + void createM3u8FileForRecord(); + +private: + float _seg_duration = 0; + uint32_t _seg_number = 0; + bool _seg_keep = false; + uint64_t _last_timestamp = 0; + uint64_t _last_seg_timestamp = 0; + uint64_t _file_index = 0; + std::string _last_file_name; + std::deque > _seg_dur_list; + bool _is_record = false; + bool _is_close_stream = false; + std::string _m3u8_file_path; + +public: + std::map _segment_file_paths; +}; + +}//namespace mediakit +#endif //HLSMAKERSUB_H diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index d4061a60..04744cfa 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -1,171 +1,170 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include -#include -#include "HlsMakerImp.h" -#include "Util/util.h" -#include "Util/uv_errno.h" -#include "Util/File.h" -#include "Common/config.h" - -using namespace std; -using namespace toolkit; - -namespace mediakit { - -HlsMakerImp::HlsMakerImp(const string &m3u8_file, - const string ¶ms, - uint32_t bufSize, - float seg_duration, - uint32_t seg_number, - bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) { - _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; - }); - - _info.folder = _path_prefix; -} - -HlsMakerImp::~HlsMakerImp() { - clearCache(false, true); -} - -void HlsMakerImp::clearCache() { - clearCache(true, false); -} - -void HlsMakerImp::clearCache(bool immediately, bool eof) { - //录制完了 - flushLastSegment(eof); - if (!isLive()||isKeep()) { - return; - } - - clear(); - _file = nullptr; - _segment_file_paths.clear(); - - //hls直播才删除文件 - GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); - if (!delay || immediately) { - File::delete_file(_path_prefix.data()); - } else { - auto path_prefix = _path_prefix; - _poller->doDelayTask(delay * 1000, [path_prefix]() { - File::delete_file(path_prefix.data()); - return 0; - }); - } -} - -string HlsMakerImp::onOpenSegment(uint64_t index) { - string segment_name, segment_path; - { - auto strDate = getTimeStr("%Y-%m-%d"); - auto strHour = getTimeStr("%H"); - auto strTime = getTimeStr("%M-%S"); - segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts"; - segment_path = _path_prefix + "/" + segment_name; - if (isLive()) { - _segment_file_paths.emplace(index, segment_path); - } - } - _file = makeFile(segment_path, true); - - //保存本切片的元数据 - _info.start_time = ::time(NULL); - _info.file_name = segment_name; - _info.file_path = segment_path; - _info.url = _info.app + "/" + _info.stream + "/" + segment_name; - - if (!_file) { - WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); - } - if (_params.empty()) { - return segment_name; - } - return segment_name + "?" + _params; -} - -void HlsMakerImp::onDelSegment(uint64_t index) { - auto it = _segment_file_paths.find(index); - if (it == _segment_file_paths.end()) { - return; - } - File::delete_file(it->second.data()); - _segment_file_paths.erase(it); -} - -void HlsMakerImp::onWriteSegment(const char *data, size_t len) { - if (_file) { - fwrite(data, len, 1, _file.get()); - } - if (_media_src) { - _media_src->onSegmentSize(len); - } -} - -void HlsMakerImp::onWriteHls(const std::string &data) { - auto hls = makeFile(_path_hls); - if (hls) { - fwrite(data.data(), data.size(), 1, hls.get()); - hls.reset(); - if (_media_src) { - _media_src->setIndexFile(data); - } - } else { - WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); - } - //DebugL << "\r\n" << string(data,len); -} - -void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { - //关闭并flush文件到磁盘 - _file = nullptr; - - GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); - if (broadcastRecordTs) { - _info.time_len = duration_ms / 1000.0f; - _info.file_size = File::fileSize(_info.file_path.data()); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); - } -} - -std::shared_ptr HlsMakerImp::makeFile(const string &file, bool setbuf) { - auto file_buf = _file_buf; - auto ret = shared_ptr(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) { - if (fp) { - fclose(fp); - } - }); - if (ret && setbuf) { - setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size); - } - return ret; -} - -void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { - _media_src = std::make_shared(vhost, app, stream_id); - _info.app = app; - _info.stream = stream_id; - _info.vhost = vhost; -} - -HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { - return _media_src; -} - +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "HlsMakerImp.h" +#include "Util/util.h" +#include "Util/uv_errno.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +HlsMakerImp::HlsMakerImp( + const string &m3u8_file, + const string ¶ms, + uint32_t bufSize, + float seg_duration, + uint32_t seg_number, + bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) { + _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; + }); + + _info.folder = _path_prefix; +} + +HlsMakerImp::~HlsMakerImp() { + clearCache(false, true); +} + +void HlsMakerImp::clearCache() { + clearCache(true, false); +} + +void HlsMakerImp::clearCache(bool immediately, bool eof) { + //录制完了 + flushLastSegment(eof); + if (!isLive()||isKeep()) { + return; + } + + clear(); + _file = nullptr; + //删除_segment_file_paths路径对应的直播文件 + for (auto it : _segment_file_paths) { + auto ts_path = it.second; + File::delete_file(ts_path.data()); + } + _segment_file_paths.clear(); + + //删除缓存的m3u8文件 + File::delete_file((_path_prefix + "/hls.m3u8").data()); +} + +string HlsMakerImp::onOpenSegment(uint64_t index) { + string segment_name, segment_path; + + auto strDate = getTimeStr("%Y-%m-%d"); + auto strHour = getTimeStr("%H"); + auto strTime = getTimeStr("%M-%S"); + segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts"; + segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name; + if ((!isKeep())) { + _segment_file_paths.emplace(index, segment_path); + } + + _file = makeFile(segment_path, true); + + //保存本切片的元数据 + _info.start_time = ::time(NULL); + _info.file_name = segment_name; + _info.file_path = segment_path; + _info.url = _info.app + "/" + _info.stream + "/" + segment_name; + + if (!_file) { + WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); + return ""; + } + if (_params.empty()) { + return strDate + "/" + strHour + "/" + segment_name; + } + return strDate + "/" + strHour + "/" + segment_name + "?" + _params; +} + +void HlsMakerImp::onDelSegment(uint64_t index) { + auto it = _segment_file_paths.find(index); + if (it == _segment_file_paths.end()) { + return; + } + File::delete_file(it->second.data()); + _segment_file_paths.erase(it); +} + +void HlsMakerImp::onWriteSegment(const char *data, size_t len) { + if (_file) { + fwrite(data, len, 1, _file.get()); + } + if (_media_src) { + _media_src->onSegmentSize(len); + } +} + +void HlsMakerImp::onWriteHls(const std::string &data) { + auto hls = makeFile(_path_hls); + if (hls) { + fwrite(data.data(), data.size(), 1, hls.get()); + hls.reset(); + if (_media_src) { + _media_src->setIndexFile(data); + } + } else { + WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); + } + //DebugL << "\r\n" << string(data,len); +} + +void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { + //关闭并flush文件到磁盘 + _file = nullptr; + + GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); + if (broadcastRecordTs) { + _info.time_len = duration_ms / 1000.0f; + _info.file_size = File::fileSize(_info.file_path.data()); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); + } +} + +std::shared_ptr HlsMakerImp::makeFile(const string &file, bool setbuf) { + auto file_buf = _file_buf; + auto ret = shared_ptr(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) { + if (fp) { + fclose(fp); + } + }); + if (ret && setbuf) { + setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size); + } + return ret; +} + +void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { + _media_src = std::make_shared(vhost, app, stream_id); + _info.app = app; + _info.stream = stream_id; + _info.vhost = vhost; +} + +HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { + return _media_src; +} + +std::string HlsMakerImp::getPathPrefix() { + return _path_prefix; +} + }//namespace mediakit \ No newline at end of file diff --git a/src/Record/HlsMakerImp.h b/src/Record/HlsMakerImp.h index 6b9acffa..31eb51f5 100644 --- a/src/Record/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -1,77 +1,78 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef HLSMAKERIMP_H -#define HLSMAKERIMP_H - -#include -#include -#include -#include "HlsMaker.h" -#include "HlsMediaSource.h" - -namespace mediakit { - -class HlsMakerImp : public HlsMaker{ -public: - HlsMakerImp(const std::string &m3u8_file, - const std::string ¶ms, - uint32_t bufSize = 64 * 1024, - float seg_duration = 5, - uint32_t seg_number = 3, - bool seg_keep = false); - - ~HlsMakerImp() override; - - /** - * 设置媒体信息 - * @param vhost 虚拟主机 - * @param app 应用名 - * @param stream_id 流id - */ - void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id); - - /** - * 获取MediaSource - * @return - */ - HlsMediaSource::Ptr getMediaSource() const; - - /** - * 清空缓存 - */ - void clearCache(); - -protected: - std::string onOpenSegment(uint64_t index) override ; - void onDelSegment(uint64_t index) override; - void onWriteSegment(const char *data, size_t len) override; - void onWriteHls(const std::string &data) override; - void onFlushLastSegment(uint64_t duration_ms) override; - -private: - std::shared_ptr makeFile(const std::string &file,bool setbuf = false); - void clearCache(bool immediately, bool eof); - -private: - int _buf_size; - std::string _params; - std::string _path_hls; - std::string _path_prefix; - RecordInfo _info; - std::shared_ptr _file; - std::shared_ptr _file_buf; - HlsMediaSource::Ptr _media_src; - toolkit::EventPoller::Ptr _poller; - std::map _segment_file_paths; -}; - -}//namespace mediakit -#endif //HLSMAKERIMP_H +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef HLSMAKERIMPSUB_H +#define HLSMAKERIMPSUB_H + +#include +#include +#include +#include "HlsMaker.h" +#include "HlsMediaSource.h" + +namespace mediakit { + +class HlsMakerImp : public HlsMaker{ +public: + HlsMakerImp( + const std::string &m3u8_file, + const std::string ¶ms, + uint32_t bufSize = 64 * 1024, + float seg_duration = 5, + uint32_t seg_number = 3, + bool seg_keep = false); + + ~HlsMakerImp() override; + + /** + * 设置媒体信息 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + */ + void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id); + + /** + * 获取MediaSource + * @return + */ + HlsMediaSource::Ptr getMediaSource() const; + + /** + * 清空缓存 + */ + void clearCache(); + +protected: + std::string onOpenSegment(uint64_t index) override ; + void onDelSegment(uint64_t index) override; + void onWriteSegment(const char *data, size_t len) override; + void onWriteHls(const std::string &data) override; + void onFlushLastSegment(uint64_t duration_ms) override; + std::string getPathPrefix() override; + +private: + std::shared_ptr makeFile(const std::string &file,bool setbuf = false); + void clearCache(bool immediately, bool eof); + +private: + int _buf_size; + std::string _params; + std::string _path_hls; + std::string _path_prefix; + RecordInfo _info; + std::shared_ptr _file; + std::shared_ptr _file_buf; + HlsMediaSource::Ptr _media_src; + +}; + +}//namespace mediakit +#endif //HLSMAKERIMPSUB_H diff --git a/src/Record/HlsMakerImpSub.cpp b/src/Record/HlsMakerImpSub.cpp deleted file mode 100644 index 3842ca30..00000000 --- a/src/Record/HlsMakerImpSub.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include -#include -#include "HlsMakerImpSub.h" -#include "Util/util.h" -#include "Util/uv_errno.h" - -using namespace std; -using namespace toolkit; - -namespace mediakit { - -HlsMakerImpSub::HlsMakerImpSub( - const string &m3u8_file, - const string ¶ms, - uint32_t bufSize, - float seg_duration, - uint32_t seg_number, - bool seg_keep):HlsMakerSub(seg_duration, seg_number, seg_keep) { - _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; - }); - - _info.folder = _path_prefix; -} - -HlsMakerImpSub::~HlsMakerImpSub() { - clearCache(false, true); -} - -void HlsMakerImpSub::clearCache() { - clearCache(true, false); -} - -void HlsMakerImpSub::clearCache(bool immediately, bool eof) { - //录制完了 - flushLastSegment(eof); - if (!isLive()||isKeep()) { - return; - } - - clear(); - _file = nullptr; - //删除_segment_file_paths路径对应的直播文件 - for (auto it : _segment_file_paths) { - auto ts_path = it.second; - File::delete_file(ts_path.data()); - } - _segment_file_paths.clear(); - - //程序异常退出的情况下,直播的8个ts文件还是无法删除, - //这里先删除掉m3u8文件对应的3个ts文件。还有保留的5个ts文件无法删除,能删除几个是几个吧。 - //fstream file(_path_prefix + "/hls.m3u8"); - //string data; - //while (getline(file,data)) { - // string ts_path = _path_prefix + "/" + data; - // File::delete_file(ts_path.data()); - //} - //file.close(); - - //删除缓存的m3u8文件 - File::delete_file((_path_prefix + "/hls.m3u8").data()); -} - -string HlsMakerImpSub::onOpenSegment(uint64_t index) { - string segment_name, segment_path; - - auto strDate = getTimeStr("%Y-%m-%d"); - auto strHour = getTimeStr("%H"); - auto strTime = getTimeStr("%M-%S"); - segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts"; - segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name; - if ((!isKeep())) { - _segment_file_paths.emplace(index, segment_path); - } - - _file = makeFile(segment_path, true); - - //保存本切片的元数据 - _info.start_time = ::time(NULL); - _info.file_name = segment_name; - _info.file_path = segment_path; - _info.url = _info.app + "/" + _info.stream + "/" + segment_name; - - if (!_file) { - WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); - return ""; - } - if (_params.empty()) { - return strDate + "/" + strHour + "/" + segment_name; - } - return strDate + "/" + strHour + "/" + segment_name + "?" + _params; -} - -void HlsMakerImpSub::onDelSegment(uint64_t index) { - auto it = _segment_file_paths.find(index); - if (it == _segment_file_paths.end()) { - return; - } - File::delete_file(it->second.data()); - _segment_file_paths.erase(it); -} - -void HlsMakerImpSub::onWriteSegment(const char *data, size_t len) { - if (_file) { - fwrite(data, len, 1, _file.get()); - } - if (_media_src) { - _media_src->onSegmentSize(len); - } -} - -void HlsMakerImpSub::onWriteHls(const std::string &data) { - auto hls = makeFile(_path_hls); - if (hls) { - fwrite(data.data(), data.size(), 1, hls.get()); - hls.reset(); - if (_media_src) { - _media_src->setIndexFile(data); - } - } else { - WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); - } - //DebugL << "\r\n" << string(data,len); -} - -void HlsMakerImpSub::onFlushLastSegment(uint64_t duration_ms) { - //关闭并flush文件到磁盘 - _file = nullptr; - - GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); - if (broadcastRecordTs) { - _info.time_len = duration_ms / 1000.0f; - _info.file_size = File::fileSize(_info.file_path.data()); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); - } -} - -std::shared_ptr HlsMakerImpSub::makeFile(const string &file, bool setbuf) { - auto file_buf = _file_buf; - auto ret = shared_ptr(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) { - if (fp) { - fclose(fp); - } - }); - if (ret && setbuf) { - setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size); - } - return ret; -} - -void HlsMakerImpSub::setMediaSource(const string &vhost, const string &app, const string &stream_id) { - _media_src = std::make_shared(vhost, app, stream_id); - _info.app = app; - _info.stream = stream_id; - _info.vhost = vhost; -} - -HlsMediaSource::Ptr HlsMakerImpSub::getMediaSource() const { - return _media_src; -} - -std::string HlsMakerImpSub::getPathPrefix() { - return _path_prefix; -} - -}//namespace mediakit \ No newline at end of file diff --git a/src/Record/HlsMakerImpSub.h b/src/Record/HlsMakerImpSub.h deleted file mode 100644 index 043937b7..00000000 --- a/src/Record/HlsMakerImpSub.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef HLSMAKERIMPSUB_H -#define HLSMAKERIMPSUB_H - -#include -#include -#include -#include "HlsMakerSub.h" -#include "HlsMediaSource.h" - -namespace mediakit { - -class HlsMakerImpSub : public HlsMakerSub{ -public: - HlsMakerImpSub( - const std::string &m3u8_file, - const std::string ¶ms, - uint32_t bufSize = 64 * 1024, - float seg_duration = 5, - uint32_t seg_number = 3, - bool seg_keep = false); - - ~HlsMakerImpSub() override; - - /** - * 设置媒体信息 - * @param vhost 虚拟主机 - * @param app 应用名 - * @param stream_id 流id - */ - void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id); - - /** - * 获取MediaSource - * @return - */ - HlsMediaSource::Ptr getMediaSource() const; - - /** - * 清空缓存 - */ - void clearCache(); - -protected: - std::string onOpenSegment(uint64_t index) override ; - void onDelSegment(uint64_t index) override; - void onWriteSegment(const char *data, size_t len) override; - void onWriteHls(const std::string &data) override; - void onFlushLastSegment(uint64_t duration_ms) override; - std::string getPathPrefix() override; - -private: - std::shared_ptr makeFile(const std::string &file,bool setbuf = false); - void clearCache(bool immediately, bool eof); - -private: - int _buf_size; - std::string _params; - std::string _path_hls; - std::string _path_prefix; - RecordInfo _info; - std::shared_ptr _file; - std::shared_ptr _file_buf; - HlsMediaSource::Ptr _media_src; - -}; - -}//namespace mediakit -#endif //HLSMAKERIMPSUB_H diff --git a/src/Record/HlsMakerSub.cpp b/src/Record/HlsMakerSub.cpp deleted file mode 100644 index 84a033ae..00000000 --- a/src/Record/HlsMakerSub.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - - #include "Util/File.h" -#include "HlsMakerSub.h" -#if defined(_WIN32) -#include -#define _access access -#else -#include -#include -#endif // WIN32 - - -using namespace std; -using namespace toolkit; - -namespace mediakit { - -HlsMakerSub::HlsMakerSub(float seg_duration, uint32_t seg_number, bool seg_keep) { - //最小允许设置为0,0个切片代表点播 - _seg_number = seg_number; - _seg_duration = seg_duration; - _seg_keep = seg_keep; - _is_record = false; - //_poller = EventPollerPool::Instance().getPoller(); -} - -HlsMakerSub::~HlsMakerSub() { - - _is_close_stream = true; -} - -void HlsMakerSub::startRecord(bool isRecord) { - //本来已经在录像,再次点击录像,或者本来已经停止录像,再次点击停止录像,直接返回 - if (isRecord == _is_record) { - return; - } - ////如果是录像,则删除之前直播的8个ts文件 - //if (isRecord) { - // std::map delete_file_paths = _segment_file_paths; - // _segment_file_paths.clear(); - // int count = 0; - // //删除_segment_file_paths路径对应的直播文件,过10s再删除,免得hls直播突然断掉 - // for (auto it : delete_file_paths) { - // count ++; - // if (count < delete_file_paths.size()) { - // auto ts_path = it.second; - // File::delete_file(ts_path.data()); - // _poller->doDelayTask(10 * 1000, [ts_path]() { - // File::delete_file(ts_path.data()); - // return 0; - // }); - // } - // } - //} - - if(isRecord) { - _seg_keep = true; - }else{ - _seg_keep = false; - _is_close_stream = true; - } - _is_record = isRecord; -} - - -void HlsMakerSub::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 sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); - } 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); - } - - m3u8.assign(file_content); - - 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); - } - - if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); - } - onWriteHls(m3u8); -} - - -void HlsMakerSub::inputData(void *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; - _last_seg_timestamp = _last_timestamp = timestamp; - } - if (is_idr_fast_packet) { - //尝试切片ts - addNewSegment(timestamp); - } - if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); - _last_timestamp = timestamp; - } - } else { - //resetTracks时触发此逻辑 - flushLastSegment(false); - } -} - -void HlsMakerSub::delOldSegment() { - if (_seg_number == 0) { - //如果设置为保留0个切片,则认为是保存为点播 - return; - } - //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 - if (_file_index > _seg_number) { - _seg_dur_list.pop_front(); - } - //如果设置为一直保存,就不删除 - if (_seg_keep) { - return; - } - GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); - //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 - if (_file_index > _seg_number + segRetain) { - onDelSegment(_file_index - _seg_number - segRetain - 1); - } -} - -void HlsMakerSub::addNewSegment(uint64_t stamp) { - if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) { - //存在上个切片,并且未到分片时间 - return; - } - - //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 - flushLastSegment(false); - - //新增切片 - _last_file_name = onOpenSegment(_file_index++); - //记录本次切片的起始时间戳 - _last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; - -} - -void HlsMakerSub::flushLastSegment(bool eof) { - if (_last_file_name.empty()) { - //不存在上个切片 - return; - } - //文件创建到最后一次数据写入的时间即为切片长度 - auto seg_dur = _last_timestamp - _last_seg_timestamp; - if (seg_dur <= 0) { - seg_dur = 100; - } - _seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name)); - delOldSegment(); - //先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 - onFlushLastSegment(seg_dur); - //然后写m3u8文件 - makeIndexFile(eof); - //判断当前是否在录像,正在录像的话,生成录像的m3u8文件 - if (_is_record) { - createM3u8FileForRecord(); - } - -} - -bool HlsMakerSub::isLive() { - return _seg_number != 0; -} - -bool HlsMakerSub::isKeep() { - return _seg_keep; -} - -void HlsMakerSub::clear() { - _file_index = 0; - _last_timestamp = 0; - _last_seg_timestamp = 0; - _seg_dur_list.clear(); - _last_file_name.clear(); - -} - -std::string HlsMakerSub::getM3u8TSBody(const std::string &file_content) { - - string new_file = file_content; - if (file_content.find("#EXT-X-ENDLIST") != file_content.npos) { - //找到了,则去掉"#EXT-X-ENDLIST" - new_file = file_content.substr(0, file_content.length() - 15); - } - - string body = new_file.substr(new_file.find_last_of("#")); - //此时的body为 - //#EXTINF:4.534, - //2022-09-14/08/2022-09-14_08-35-16.ts - string extinf = body.substr(0, body.find(",")+2); - string tsFile = body.substr(body.find_last_of("/") + 1); - body.append("#EXT-X-ENDLIST\n"); - - return extinf + tsFile + "#EXT-X-ENDLIST\n"; -} - -std::string HlsMakerSub::getTsFile(const std::string &file_content) { - // 最后一个TS的body为 - // 2022-09-13/13/58-13_43.ts - // #EXT-X-ENDLIST - string body = file_content.substr(file_content.find_last_of(",") + 2); - string ts_file_name = body.substr(body.find_last_of("/") + 1); - if (ts_file_name.find("#EXT-X-ENDLIST") == ts_file_name.npos ) { - ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 4); //没找到,去掉“.ts\n”,只留名字 - } else { - ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 19); //找到的话,去掉“.ts\n#EXT-X-ENDLIST\n”,只留名字 - } - - return ts_file_name; -} - -void HlsMakerSub::createM3u8FileForRecord() { - // 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长,并生成m3u8文件的路径 - string live_file = File::loadFile((getPathPrefix() + "/hls.m3u8").data()); - if (live_file.empty()) { - return; - } - - string body = getM3u8TSBody(live_file); - string ts_file_name = getTsFile(live_file); // ts_file: 2022-09-14_11-06-03 - string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/"; - - // 2.判断该目录下有没有m3u8文件,没有的话,生成第一个m3u8文件,有的话,重命名 - int handle = -1; - DIR *dir_info = opendir(m3u8_file.data()); - struct dirent *dir_entry; - if (dir_info) { - while ((dir_entry =readdir(dir_info)) != NULL) { - if (end_with(dir_entry->d_name, ".m3u8")) { - handle = 0; - break; - } - } - closedir(dir_info); - } else { - return; - } - - if (-1 == handle) {//第一次播放流 - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; - _is_close_stream = false; - } else {//断流过,一次以上播放 - if (_is_close_stream) { - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; - _is_close_stream = false; - } - if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空 - _m3u8_file_path = m3u8_file + ts_file_name + ".m3u8"; - } - } - - if (_m3u8_file_path.empty()) { - WarnL << "create m3u8 file failed, _m3u8_file_path is empty." ; - return; - } - - //3.写m3u8文件 - string m3u8Header = "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:2\n" - "#EXT-X-MEDIA-SEQUENCE:0\n"; - - if (access(_m3u8_file_path.data(), 0) != 0) { //文件不存在 - auto file = File::create_file(_m3u8_file_path.data(), "wb"); - if (file) { - fwrite(m3u8Header.data(), m3u8Header.size(), 1, file); - fwrite(body.data(), body.size(), 1, file); - fclose(file); - } - } else { - // 第二次进来,去掉 "#EXT-X-ENDLIST\n",再重新追加file_content,保存文件 - auto file = File::create_file(_m3u8_file_path.data(), "r+"); - if (file) { - fseek(file, -15, SEEK_END); - fwrite(body.data(), body.size(), 1, file); - fclose(file); - } - } -} - -}//namespace mediakit diff --git a/src/Record/HlsMakerSub.h b/src/Record/HlsMakerSub.h deleted file mode 100644 index 330447c4..00000000 --- a/src/Record/HlsMakerSub.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). - * - * Use of this source code is governed by MIT license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef HLSMAKERSUB_H -#define HLSMAKERSUB_H - -#include -#include -#include "Common/config.h" -#include "Util/TimeTicker.h" -#include "Util/File.h" -#include "Util/util.h" -#include "Util/logger.h" -//#include "ZLToolKit/src/Poller/EventPoller.h" -namespace mediakit { - -class HlsMakerSub { -public: - /** - * @param seg_duration 切片文件长度 - * @param seg_number 切片个数 - * @param seg_keep 是否保留切片文件 - */ - HlsMakerSub(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); - virtual ~HlsMakerSub(); - - /** - * 写入ts数据 - * @param data 数据 - * @param len 数据长度 - * @param timestamp 毫秒时间戳 - * @param is_idr_fast_packet 是否为关键帧第一个包 - */ - void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); - - /** - * 是否为直播 - */ - bool isLive(); - - /** - * 是否保留切片文件 - */ - bool isKeep(); - - /** - * 清空记录 - */ - void clear(); - //设置是否录像标志 - void startRecord(bool isRecord); - -protected: - /** - * 创建ts切片文件回调 - * @param index - * @return - */ - virtual std::string onOpenSegment(uint64_t index) = 0; - - /** - * 删除ts切片文件回调 - * @param index - */ - virtual void onDelSegment(uint64_t index) = 0; - - /** - * 写ts切片文件回调 - * @param data - * @param len - */ - virtual void onWriteSegment(const char *data, size_t len) = 0; - - /** - * 写m3u8文件回调 - */ - virtual void onWriteHls(const std::string &data) = 0; - - /** - * 上一个 ts 切片写入完成, 可在这里进行通知处理 - * @param duration_ms 上一个 ts 切片的时长, 单位为毫秒 - */ - virtual void onFlushLastSegment(uint64_t duration_ms) {}; - virtual std::string getPathPrefix() = 0; - - /** - * 关闭上个ts切片并且写入m3u8索引 - * @param eof HLS直播是否已结束 - */ - void flushLastSegment(bool eof); - -private: - /** - * 生成m3u8文件 - * @param eof true代表点播 - */ - void makeIndexFile(bool eof = false); - - /** - * 删除旧的ts切片 - */ - void delOldSegment(); - - /** - * 添加新的ts切片 - * @param timestamp - */ - void addNewSegment(uint64_t timestamp); - - //新增函数,实现录像功能 - std::string getTsFile(const std::string &file_content); - std::string getM3u8TSBody(const std::string &file_content); - void createM3u8FileForRecord(); - -private: - float _seg_duration = 0; - uint32_t _seg_number = 0; - bool _seg_keep = false; - uint64_t _last_timestamp = 0; - uint64_t _last_seg_timestamp = 0; - uint64_t _file_index = 0; - std::string _last_file_name; - std::deque > _seg_dur_list; - bool _is_record = false; - bool _is_close_stream = false; - std::string _m3u8_file_path; - //toolkit::EventPoller::Ptr _poller; - -public: - std::map _segment_file_paths; -}; - -}//namespace mediakit -#endif //HLSMAKERSUB_H diff --git a/src/Record/HlsRecorder.h b/src/Record/HlsRecorder.h index 49965b1d..8d5fd400 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -11,8 +11,7 @@ #ifndef HLSRECORDER_H #define HLSRECORDER_H -//#include "HlsMakerImp.h" -#include "HlsMakerImpSub.h" +#include "HlsMakerImp.h" #include "MPEG.h" #include "Common/config.h" @@ -27,9 +26,8 @@ public: GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); - // _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); _option = option; - _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); //清空上次的残余文件 _hls->clearCache(); } @@ -76,10 +74,11 @@ public: } void startRecord(bool flag) { _hls->startRecord(flag); - _isRecord = flag; + _is_record = flag; } - bool getRecordFlag() { return _isRecord; } + bool getRecordFlag() { return _is_record; } + private: void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { if (!buffer) { @@ -92,10 +91,9 @@ private: private: bool _enabled = true; bool _clear_cache = false; - //std::shared_ptr _hls; ProtocolOption _option; - std::shared_ptr _hls; - bool _isRecord = false; + std::shared_ptr _hls; + bool _is_record = false; }; }//namespace mediakit #endif //HLSRECORDER_H