添加hls录像功能

This commit is contained in:
renlu 2022-12-28 11:02:02 +08:00
parent cf0da71845
commit 82af21f910
9 changed files with 702 additions and 1273 deletions

View File

@ -1,169 +1,308 @@
/* /*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include "HlsMaker.h" #include "Util/File.h"
#include "Common/config.h" #include "HlsMaker.h"
#if defined(_WIN32)
using namespace std; #include <io.h>
#define _access access
namespace mediakit { #else
#include <unistd.h>
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { #include <dirent.h>
//最小允许设置为00个切片代表点播 #endif // WIN32
_seg_number = seg_number;
_seg_duration = seg_duration;
_seg_keep = seg_keep; using namespace std;
} using namespace toolkit;
HlsMaker::~HlsMaker() { namespace mediakit {
}
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
//最小允许设置为00个切片代表点播
void HlsMaker::makeIndexFile(bool eof) { _seg_number = seg_number;
char file_content[1024]; _seg_duration = seg_duration;
int maxSegmentDuration = 0; _seg_keep = seg_keep;
_is_record = false;
for (auto &tp : _seg_dur_list) { }
int dur = std::get<0>(tp);
if (dur > maxSegmentDuration) { HlsMaker::~HlsMaker() {
maxSegmentDuration = dur;
} _is_close_stream = true;
} }
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; void HlsMaker::startRecord(bool isRecord) {
//本来已经在录像,再次点击录像,或者本来已经停止录像,再次点击停止录像,直接返回
string m3u8; if (isRecord == _is_record) {
if (_seg_number == 0) { return;
// 录像点播支持时移 }
snprintf(file_content, sizeof(file_content),
"#EXTM3U\n" if(isRecord) {
"#EXT-X-PLAYLIST-TYPE:EVENT\n" _seg_keep = true;
"#EXT-X-VERSION:4\n" }else{
"#EXT-X-TARGETDURATION:%u\n" _seg_keep = false;
"#EXT-X-MEDIA-SEQUENCE:%llu\n", _is_close_stream = true;
(maxSegmentDuration + 999) / 1000, }
sequence); _is_record = isRecord;
} else { }
snprintf(file_content, sizeof(file_content),
"#EXTM3U\n"
"#EXT-X-VERSION:3\n" void HlsMaker::makeIndexFile(bool eof) {
"#EXT-X-ALLOW-CACHE:NO\n" char file_content[1024];
"#EXT-X-TARGETDURATION:%u\n" int maxSegmentDuration = 0;
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000, for (auto &tp : _seg_dur_list) {
sequence); int dur = std::get<0>(tp);
} if (dur > maxSegmentDuration) {
maxSegmentDuration = dur;
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()); auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
m3u8.append(file_content);
} string m3u8;
if (_seg_number == 0) {
if (eof) { // 录像点播支持时移
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); snprintf(file_content, sizeof(file_content),
m3u8.append(file_content); "#EXTM3U\n"
} "#EXT-X-PLAYLIST-TYPE:EVENT\n"
onWriteHls(m3u8); "#EXT-X-VERSION:4\n"
} "#EXT-X-TARGETDURATION:%u\n"
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000,
void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { sequence);
if (data && len) { } else {
if (timestamp < _last_timestamp) { snprintf(file_content, sizeof(file_content),
//时间戳回退了,切片时长重新计时 "#EXTM3U\n"
WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; "#EXT-X-VERSION:3\n"
_last_seg_timestamp = _last_timestamp = timestamp; "#EXT-X-ALLOW-CACHE:NO\n"
} "#EXT-X-TARGETDURATION:%u\n"
if (is_idr_fast_packet) { "#EXT-X-MEDIA-SEQUENCE:%llu\n",
//尝试切片ts (maxSegmentDuration + 999) / 1000,
addNewSegment(timestamp); sequence);
} }
if (!_last_file_name.empty()) {
//存在切片才写入ts数据 m3u8.assign(file_content);
onWriteSegment((char *) data, len);
_last_timestamp = timestamp; 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());
} else { m3u8.append(file_content);
//resetTracks时触发此逻辑 }
flushLastSegment(false);
} if (eof) {
} snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
m3u8.append(file_content);
void HlsMaker::delOldSegment() { }
if (_seg_number == 0) { onWriteHls(m3u8);
//如果设置为保留0个切片则认为是保存为点播 }
return;
}
//在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
if (_file_index > _seg_number) { if (data && len) {
_seg_dur_list.pop_front(); if (timestamp < _last_timestamp) {
} //时间戳回退了,切片时长重新计时
//如果设置为一直保存,就不删除 WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp;
if (_seg_keep) { _last_seg_timestamp = _last_timestamp = timestamp;
return; }
} if (is_idr_fast_packet) {
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain); //尝试切片ts
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 addNewSegment(timestamp);
if (_file_index > _seg_number + segRetain) { }
onDelSegment(_file_index - _seg_number - segRetain - 1); if (!_last_file_name.empty()) {
} //存在切片才写入ts数据
} onWriteSegment((char *) data, len);
_last_timestamp = timestamp;
void HlsMaker::addNewSegment(uint64_t stamp) { }
if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) { } else {
//存在上个切片,并且未到分片时间 //resetTracks时触发此逻辑
return; flushLastSegment(false);
} }
}
//关闭并保存上一个切片如果_seg_number==0,那么是点播。
flushLastSegment(false); void HlsMaker::delOldSegment() {
//新增切片 if (_seg_number == 0) {
_last_file_name = onOpenSegment(_file_index++); //如果设置为保留0个切片则认为是保存为点播
//记录本次切片的起始时间戳 return;
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp; }
} //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致
if (_file_index > _seg_number) {
void HlsMaker::flushLastSegment(bool eof){ _seg_dur_list.pop_front();
if (_last_file_name.empty()) { }
//不存在上个切片 //如果设置为一直保存,就不删除
return; if (_seg_keep) {
} return;
//文件创建到最后一次数据写入的时间即为切片长度 }
auto seg_dur = _last_timestamp - _last_seg_timestamp; GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
if (seg_dur <= 0) { //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
seg_dur = 100; if (_file_index > _seg_number + segRetain) {
} onDelSegment(_file_index - _seg_number - segRetain - 1);
_seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name)); }
delOldSegment(); }
//先flush ts切片否则可能存在ts文件未写入完毕就被访问的情况
onFlushLastSegment(seg_dur); void HlsMaker::addNewSegment(uint64_t stamp) {
//然后写m3u8文件 if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) {
makeIndexFile(eof); //存在上个切片,并且未到分片时间
} return;
}
bool HlsMaker::isLive() {
return _seg_number != 0; //关闭并保存上一个切片如果_seg_number==0,那么是点播。
} flushLastSegment(false);
bool HlsMaker::isKeep() { //新增切片
return _seg_keep; _last_file_name = onOpenSegment(_file_index++);
} //记录本次切片的起始时间戳
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp;
void HlsMaker::clear() {
_file_index = 0; }
_last_timestamp = 0;
_last_seg_timestamp = 0; void HlsMaker::flushLastSegment(bool eof) {
_seg_dur_list.clear(); if (_last_file_name.empty()) {
_last_file_name.clear(); //不存在上个切片
} return;
}
}//namespace mediakit //文件创建到最后一次数据写入的时间即为切片长度
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

View File

@ -1,122 +1,140 @@
/* /*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef HLSMAKER_H #ifndef HLSMAKERSUB_H
#define HLSMAKER_H #define HLSMAKERSUB_H
#include <string> #include <deque>
#include <deque> #include <tuple>
#include <tuple> #include "Common/config.h"
#include "Util/TimeTicker.h"
namespace mediakit { #include "Util/File.h"
#include "Util/util.h"
class HlsMaker { #include "Util/logger.h"
public:
/** namespace mediakit {
* @param seg_duration
* @param seg_number class HlsMaker {
* @param seg_keep public:
*/ /**
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); * @param seg_duration
virtual ~HlsMaker(); * @param seg_number
* @param seg_keep
/** */
* ts数据 HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
* @param data virtual ~HlsMaker();
* @param len
* @param timestamp /**
* @param is_idr_fast_packet * ts数据
*/ * @param data
void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); * @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 isLive();
bool isKeep();
/**
/** *
* */
*/ bool isKeep();
void clear();
/**
protected: *
/** */
* ts切片文件回调 void clear();
* @param index //设置是否录像标志
* @return void startRecord(bool isRecord);
*/
virtual std::string onOpenSegment(uint64_t index) = 0; protected:
/**
/** * ts切片文件回调
* ts切片文件回调 * @param index
* @param index * @return
*/ */
virtual void onDelSegment(uint64_t index) = 0; virtual std::string onOpenSegment(uint64_t index) = 0;
/** /**
* ts切片文件回调 * ts切片文件回调
* @param data * @param index
* @param len */
*/ virtual void onDelSegment(uint64_t index) = 0;
virtual void onWriteSegment(const char *data, size_t len) = 0;
/**
/** * ts切片文件回调
* m3u8文件回调 * @param data
*/ * @param len
virtual void onWriteHls(const std::string &data) = 0; */
virtual void onWriteSegment(const char *data, size_t len) = 0;
/**
* ts , /**
* @param duration_ms ts , * m3u8文件回调
*/ */
virtual void onFlushLastSegment(uint64_t duration_ms) {}; virtual void onWriteHls(const std::string &data) = 0;
/** /**
* ts切片并且写入m3u8索引 * ts ,
* @param eof HLS直播是否已结束 * @param duration_ms ts ,
*/ */
void flushLastSegment(bool eof); virtual void onFlushLastSegment(uint64_t duration_ms) {};
virtual std::string getPathPrefix() = 0;
private:
/** /**
* m3u8文件 * ts切片并且写入m3u8索引
* @param eof true代表点播 * @param eof HLS直播是否已结束
*/ */
void makeIndexFile(bool eof = false); void flushLastSegment(bool eof);
/** private:
* ts切片 /**
*/ * m3u8文件
void delOldSegment(); * @param eof true代表点播
*/
/** void makeIndexFile(bool eof = false);
* ts切片
* @param timestamp /**
*/ * ts切片
void addNewSegment(uint64_t timestamp); */
void delOldSegment();
private:
float _seg_duration = 0; /**
uint32_t _seg_number = 0; * ts切片
bool _seg_keep = false; * @param timestamp
uint64_t _last_timestamp = 0; */
uint64_t _last_seg_timestamp = 0; void addNewSegment(uint64_t timestamp);
uint64_t _file_index = 0;
std::string _last_file_name; //新增函数,实现录像功能
std::deque<std::tuple<int,std::string> > _seg_dur_list; std::string getTsFile(const std::string &file_content);
}; std::string getM3u8TSBody(const std::string &file_content);
void createM3u8FileForRecord();
}//namespace mediakit
#endif //HLSMAKER_H 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<std::tuple<int,std::string> > _seg_dur_list;
bool _is_record = false;
bool _is_close_stream = false;
std::string _m3u8_file_path;
public:
std::map<uint64_t /*index*/, std::string /*file_path*/> _segment_file_paths;
};
}//namespace mediakit
#endif //HLSMAKERSUB_H

View File

@ -1,171 +1,170 @@
/* /*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include <ctime> #include <ctime>
#include <sys/stat.h> #include <sys/stat.h>
#include "HlsMakerImp.h" #include "HlsMakerImp.h"
#include "Util/util.h" #include "Util/util.h"
#include "Util/uv_errno.h" #include "Util/uv_errno.h"
#include "Util/File.h"
#include "Common/config.h" using namespace std;
using namespace toolkit;
using namespace std;
using namespace toolkit; namespace mediakit {
namespace mediakit { HlsMakerImp::HlsMakerImp(
const string &m3u8_file,
HlsMakerImp::HlsMakerImp(const string &m3u8_file, const string &params,
const string &params, uint32_t bufSize,
uint32_t bufSize, float seg_duration,
float seg_duration, uint32_t seg_number,
uint32_t seg_number, bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) { _path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
_poller = EventPollerPool::Instance().getPoller(); _path_hls = m3u8_file;
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/')); _params = params;
_path_hls = m3u8_file; _buf_size = bufSize;
_params = params; _file_buf.reset(new char[bufSize], [](char *ptr) {
_buf_size = bufSize; delete[] ptr;
_file_buf.reset(new char[bufSize], [](char *ptr) { });
delete[] ptr;
}); _info.folder = _path_prefix;
}
_info.folder = _path_prefix;
} HlsMakerImp::~HlsMakerImp() {
clearCache(false, true);
HlsMakerImp::~HlsMakerImp() { }
clearCache(false, true);
} void HlsMakerImp::clearCache() {
clearCache(true, false);
void HlsMakerImp::clearCache() { }
clearCache(true, false);
} void HlsMakerImp::clearCache(bool immediately, bool eof) {
//录制完了
void HlsMakerImp::clearCache(bool immediately, bool eof) { flushLastSegment(eof);
//录制完了 if (!isLive()||isKeep()) {
flushLastSegment(eof); return;
if (!isLive()||isKeep()) { }
return;
} clear();
_file = nullptr;
clear(); //删除_segment_file_paths路径对应的直播文件
_file = nullptr; for (auto it : _segment_file_paths) {
_segment_file_paths.clear(); auto ts_path = it.second;
File::delete_file(ts_path.data());
//hls直播才删除文件 }
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); _segment_file_paths.clear();
if (!delay || immediately) {
File::delete_file(_path_prefix.data()); //删除缓存的m3u8文件
} else { File::delete_file((_path_prefix + "/hls.m3u8").data());
auto path_prefix = _path_prefix; }
_poller->doDelayTask(delay * 1000, [path_prefix]() {
File::delete_file(path_prefix.data()); string HlsMakerImp::onOpenSegment(uint64_t index) {
return 0; string segment_name, segment_path;
});
} auto strDate = getTimeStr("%Y-%m-%d");
} auto strHour = getTimeStr("%H");
auto strTime = getTimeStr("%M-%S");
string HlsMakerImp::onOpenSegment(uint64_t index) { segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts";
string segment_name, segment_path; segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name;
{ if ((!isKeep())) {
auto strDate = getTimeStr("%Y-%m-%d"); _segment_file_paths.emplace(index, segment_path);
auto strHour = getTimeStr("%H"); }
auto strTime = getTimeStr("%M-%S");
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts"; _file = makeFile(segment_path, true);
segment_path = _path_prefix + "/" + segment_name;
if (isLive()) { //保存本切片的元数据
_segment_file_paths.emplace(index, segment_path); _info.start_time = ::time(NULL);
} _info.file_name = segment_name;
} _info.file_path = segment_path;
_file = makeFile(segment_path, true); _info.url = _info.app + "/" + _info.stream + "/" + segment_name;
//保存本切片的元数据 if (!_file) {
_info.start_time = ::time(NULL); WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
_info.file_name = segment_name; return "";
_info.file_path = segment_path; }
_info.url = _info.app + "/" + _info.stream + "/" + segment_name; if (_params.empty()) {
return strDate + "/" + strHour + "/" + segment_name;
if (!_file) { }
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); return strDate + "/" + strHour + "/" + segment_name + "?" + _params;
} }
if (_params.empty()) {
return segment_name; void HlsMakerImp::onDelSegment(uint64_t index) {
} auto it = _segment_file_paths.find(index);
return segment_name + "?" + _params; if (it == _segment_file_paths.end()) {
} return;
}
void HlsMakerImp::onDelSegment(uint64_t index) { File::delete_file(it->second.data());
auto it = _segment_file_paths.find(index); _segment_file_paths.erase(it);
if (it == _segment_file_paths.end()) { }
return;
} void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
File::delete_file(it->second.data()); if (_file) {
_segment_file_paths.erase(it); fwrite(data, len, 1, _file.get());
} }
if (_media_src) {
void HlsMakerImp::onWriteSegment(const char *data, size_t len) { _media_src->onSegmentSize(len);
if (_file) { }
fwrite(data, len, 1, _file.get()); }
}
if (_media_src) { void HlsMakerImp::onWriteHls(const std::string &data) {
_media_src->onSegmentSize(len); auto hls = makeFile(_path_hls);
} if (hls) {
} fwrite(data.data(), data.size(), 1, hls.get());
hls.reset();
void HlsMakerImp::onWriteHls(const std::string &data) { if (_media_src) {
auto hls = makeFile(_path_hls); _media_src->setIndexFile(data);
if (hls) { }
fwrite(data.data(), data.size(), 1, hls.get()); } else {
hls.reset(); WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
if (_media_src) { }
_media_src->setIndexFile(data); //DebugL << "\r\n" << string(data,len);
} }
} else {
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
} //关闭并flush文件到磁盘
//DebugL << "\r\n" << string(data,len); _file = nullptr;
}
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { if (broadcastRecordTs) {
//关闭并flush文件到磁盘 _info.time_len = duration_ms / 1000.0f;
_file = nullptr; _info.file_size = File::fileSize(_info.file_path.data());
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info);
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); }
if (broadcastRecordTs) { }
_info.time_len = duration_ms / 1000.0f;
_info.file_size = File::fileSize(_info.file_path.data()); std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); auto file_buf = _file_buf;
} auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
} if (fp) {
fclose(fp);
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) { }
auto file_buf = _file_buf; });
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) { if (ret && setbuf) {
if (fp) { setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
fclose(fp); }
} return ret;
}); }
if (ret && setbuf) {
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size); void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
} _media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
return ret; _info.app = app;
} _info.stream = stream_id;
_info.vhost = vhost;
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { }
_media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
_info.app = app; HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
_info.stream = stream_id; return _media_src;
_info.vhost = vhost; }
}
std::string HlsMakerImp::getPathPrefix() {
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { return _path_prefix;
return _media_src; }
}
}//namespace mediakit }//namespace mediakit

View File

@ -1,77 +1,78 @@
/* /*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef HLSMAKERIMP_H #ifndef HLSMAKERIMPSUB_H
#define HLSMAKERIMP_H #define HLSMAKERIMPSUB_H
#include <memory> #include <memory>
#include <string> #include <string>
#include <stdlib.h> #include <stdlib.h>
#include "HlsMaker.h" #include "HlsMaker.h"
#include "HlsMediaSource.h" #include "HlsMediaSource.h"
namespace mediakit { namespace mediakit {
class HlsMakerImp : public HlsMaker{ class HlsMakerImp : public HlsMaker{
public: public:
HlsMakerImp(const std::string &m3u8_file, HlsMakerImp(
const std::string &params, const std::string &m3u8_file,
uint32_t bufSize = 64 * 1024, const std::string &params,
float seg_duration = 5, uint32_t bufSize = 64 * 1024,
uint32_t seg_number = 3, float seg_duration = 5,
bool seg_keep = false); uint32_t seg_number = 3,
bool seg_keep = false);
~HlsMakerImp() override;
~HlsMakerImp() override;
/**
* /**
* @param vhost *
* @param app * @param vhost
* @param stream_id id * @param app
*/ * @param stream_id id
void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id); */
void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id);
/**
* MediaSource /**
* @return * MediaSource
*/ * @return
HlsMediaSource::Ptr getMediaSource() const; */
HlsMediaSource::Ptr getMediaSource() const;
/**
* /**
*/ *
void clearCache(); */
void clearCache();
protected:
std::string onOpenSegment(uint64_t index) override ; protected:
void onDelSegment(uint64_t index) override; std::string onOpenSegment(uint64_t index) override ;
void onWriteSegment(const char *data, size_t len) override; void onDelSegment(uint64_t index) override;
void onWriteHls(const std::string &data) override; void onWriteSegment(const char *data, size_t len) override;
void onFlushLastSegment(uint64_t duration_ms) override; void onWriteHls(const std::string &data) override;
void onFlushLastSegment(uint64_t duration_ms) override;
private: std::string getPathPrefix() override;
std::shared_ptr<FILE> makeFile(const std::string &file,bool setbuf = false);
void clearCache(bool immediately, bool eof); private:
std::shared_ptr<FILE> makeFile(const std::string &file,bool setbuf = false);
private: void clearCache(bool immediately, bool eof);
int _buf_size;
std::string _params; private:
std::string _path_hls; int _buf_size;
std::string _path_prefix; std::string _params;
RecordInfo _info; std::string _path_hls;
std::shared_ptr<FILE> _file; std::string _path_prefix;
std::shared_ptr<char> _file_buf; RecordInfo _info;
HlsMediaSource::Ptr _media_src; std::shared_ptr<FILE> _file;
toolkit::EventPoller::Ptr _poller; std::shared_ptr<char> _file_buf;
std::map<uint64_t/*index*/,std::string/*file_path*/> _segment_file_paths; HlsMediaSource::Ptr _media_src;
};
};
}//namespace mediakit
#endif //HLSMAKERIMP_H }//namespace mediakit
#endif //HLSMAKERIMPSUB_H

View File

@ -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 <ctime>
#include <sys/stat.h>
#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 &params,
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<FILE> HlsMakerImpSub::makeFile(const string &file, bool setbuf) {
auto file_buf = _file_buf;
auto ret = shared_ptr<FILE>(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<HlsMediaSource>(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

View File

@ -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 <memory>
#include <string>
#include <stdlib.h>
#include "HlsMakerSub.h"
#include "HlsMediaSource.h"
namespace mediakit {
class HlsMakerImpSub : public HlsMakerSub{
public:
HlsMakerImpSub(
const std::string &m3u8_file,
const std::string &params,
uint32_t bufSize = 64 * 1024,
float seg_duration = 5,
uint32_t seg_number = 3,
bool seg_keep = false);
~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<FILE> 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> _file;
std::shared_ptr<char> _file_buf;
HlsMediaSource::Ptr _media_src;
};
}//namespace mediakit
#endif //HLSMAKERIMPSUB_H

View File

@ -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 <io.h>
#define _access access
#else
#include <unistd.h>
#include <dirent.h>
#endif // WIN32
using namespace std;
using namespace toolkit;
namespace mediakit {
HlsMakerSub::HlsMakerSub(float seg_duration, uint32_t seg_number, bool seg_keep) {
//最小允许设置为00个切片代表点播
_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<uint64_t, std::string> 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

View File

@ -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 <deque>
#include <tuple>
#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<std::tuple<int,std::string> > _seg_dur_list;
bool _is_record = false;
bool _is_close_stream = false;
std::string _m3u8_file_path;
//toolkit::EventPoller::Ptr _poller;
public:
std::map<uint64_t /*index*/, std::string /*file_path*/> _segment_file_paths;
};
}//namespace mediakit
#endif //HLSMAKERSUB_H

View File

@ -11,8 +11,7 @@
#ifndef HLSRECORDER_H #ifndef HLSRECORDER_H
#define HLSRECORDER_H #define HLSRECORDER_H
//#include "HlsMakerImp.h" #include "HlsMakerImp.h"
#include "HlsMakerImpSub.h"
#include "MPEG.h" #include "MPEG.h"
#include "Common/config.h" #include "Common/config.h"
@ -27,9 +26,8 @@ public:
GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep);
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
// _hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
_option = option; _option = option;
_hls = std::make_shared<HlsMakerImpSub>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); _hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
//清空上次的残余文件 //清空上次的残余文件
_hls->clearCache(); _hls->clearCache();
} }
@ -76,10 +74,11 @@ public:
} }
void startRecord(bool flag) { void startRecord(bool flag) {
_hls->startRecord(flag); _hls->startRecord(flag);
_isRecord = flag; _is_record = flag;
} }
bool getRecordFlag() { return _isRecord; } bool getRecordFlag() { return _is_record; }
private: private:
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override { void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
if (!buffer) { if (!buffer) {
@ -92,10 +91,9 @@ private:
private: private:
bool _enabled = true; bool _enabled = true;
bool _clear_cache = false; bool _clear_cache = false;
//std::shared_ptr<HlsMakerImp> _hls;
ProtocolOption _option; ProtocolOption _option;
std::shared_ptr<HlsMakerImpSub> _hls; std::shared_ptr<HlsMakerImp> _hls;
bool _isRecord = false; bool _is_record = false;
}; };
}//namespace mediakit }//namespace mediakit
#endif //HLSRECORDER_H #endif //HLSRECORDER_H