mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-23 01:57:50 +08:00
添加hls录像功能
This commit is contained in:
parent
cf0da71845
commit
82af21f910
@ -8,10 +8,19 @@
|
|||||||
* 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 "Util/File.h"
|
||||||
#include "HlsMaker.h"
|
#include "HlsMaker.h"
|
||||||
#include "Common/config.h"
|
#if defined(_WIN32)
|
||||||
|
#include <io.h>
|
||||||
|
#define _access access
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#endif // WIN32
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
@ -20,9 +29,27 @@ HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
|
|||||||
_seg_number = seg_number;
|
_seg_number = seg_number;
|
||||||
_seg_duration = seg_duration;
|
_seg_duration = seg_duration;
|
||||||
_seg_keep = seg_keep;
|
_seg_keep = seg_keep;
|
||||||
|
_is_record = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HlsMaker::~HlsMaker() {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -126,13 +153,15 @@ void HlsMaker::addNewSegment(uint64_t stamp) {
|
|||||||
|
|
||||||
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。
|
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。
|
||||||
flushLastSegment(false);
|
flushLastSegment(false);
|
||||||
|
|
||||||
//新增切片
|
//新增切片
|
||||||
_last_file_name = onOpenSegment(_file_index++);
|
_last_file_name = onOpenSegment(_file_index++);
|
||||||
//记录本次切片的起始时间戳
|
//记录本次切片的起始时间戳
|
||||||
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp;
|
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMaker::flushLastSegment(bool eof){
|
void HlsMaker::flushLastSegment(bool eof) {
|
||||||
if (_last_file_name.empty()) {
|
if (_last_file_name.empty()) {
|
||||||
//不存在上个切片
|
//不存在上个切片
|
||||||
return;
|
return;
|
||||||
@ -148,6 +177,11 @@ void HlsMaker::flushLastSegment(bool eof){
|
|||||||
onFlushLastSegment(seg_dur);
|
onFlushLastSegment(seg_dur);
|
||||||
//然后写m3u8文件
|
//然后写m3u8文件
|
||||||
makeIndexFile(eof);
|
makeIndexFile(eof);
|
||||||
|
//判断当前是否在录像,正在录像的话,生成录像的m3u8文件
|
||||||
|
if (_is_record) {
|
||||||
|
createM3u8FileForRecord();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HlsMaker::isLive() {
|
bool HlsMaker::isLive() {
|
||||||
@ -164,6 +198,111 @@ void HlsMaker::clear() {
|
|||||||
_last_seg_timestamp = 0;
|
_last_seg_timestamp = 0;
|
||||||
_seg_dur_list.clear();
|
_seg_dur_list.clear();
|
||||||
_last_file_name.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
|
}//namespace mediakit
|
||||||
|
|||||||
@ -8,12 +8,16 @@
|
|||||||
* 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"
|
||||||
|
#include "Util/File.h"
|
||||||
|
#include "Util/util.h"
|
||||||
|
#include "Util/logger.h"
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
@ -50,6 +54,8 @@ public:
|
|||||||
* 清空记录
|
* 清空记录
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
//设置是否录像标志
|
||||||
|
void startRecord(bool isRecord);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
@ -82,6 +88,7 @@ protected:
|
|||||||
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
|
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
|
||||||
*/
|
*/
|
||||||
virtual void onFlushLastSegment(uint64_t duration_ms) {};
|
virtual void onFlushLastSegment(uint64_t duration_ms) {};
|
||||||
|
virtual std::string getPathPrefix() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭上个ts切片并且写入m3u8索引
|
* 关闭上个ts切片并且写入m3u8索引
|
||||||
@ -107,6 +114,11 @@ private:
|
|||||||
*/
|
*/
|
||||||
void addNewSegment(uint64_t 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:
|
private:
|
||||||
float _seg_duration = 0;
|
float _seg_duration = 0;
|
||||||
uint32_t _seg_number = 0;
|
uint32_t _seg_number = 0;
|
||||||
@ -116,7 +128,13 @@ private:
|
|||||||
uint64_t _file_index = 0;
|
uint64_t _file_index = 0;
|
||||||
std::string _last_file_name;
|
std::string _last_file_name;
|
||||||
std::deque<std::tuple<int,std::string> > _seg_dur_list;
|
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
|
}//namespace mediakit
|
||||||
#endif //HLSMAKER_H
|
#endif //HLSMAKERSUB_H
|
||||||
|
|||||||
@ -13,21 +13,19 @@
|
|||||||
#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 std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
HlsMakerImp::HlsMakerImp(
|
||||||
|
const string &m3u8_file,
|
||||||
const string ¶ms,
|
const string ¶ms,
|
||||||
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) {
|
||||||
_poller = EventPollerPool::Instance().getPoller();
|
|
||||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||||
_path_hls = m3u8_file;
|
_path_hls = m3u8_file;
|
||||||
_params = params;
|
_params = params;
|
||||||
@ -56,33 +54,29 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
|||||||
|
|
||||||
clear();
|
clear();
|
||||||
_file = nullptr;
|
_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();
|
_segment_file_paths.clear();
|
||||||
|
|
||||||
//hls直播才删除文件
|
//删除缓存的m3u8文件
|
||||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
File::delete_file((_path_prefix + "/hls.m3u8").data());
|
||||||
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 HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||||
string segment_name, segment_path;
|
string segment_name, segment_path;
|
||||||
{
|
|
||||||
auto strDate = getTimeStr("%Y-%m-%d");
|
auto strDate = getTimeStr("%Y-%m-%d");
|
||||||
auto strHour = getTimeStr("%H");
|
auto strHour = getTimeStr("%H");
|
||||||
auto strTime = getTimeStr("%M-%S");
|
auto strTime = getTimeStr("%M-%S");
|
||||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
segment_name = StrPrinter << strDate + "_" + strHour + "-" + strTime << ".ts";
|
||||||
segment_path = _path_prefix + "/" + segment_name;
|
segment_path = _path_prefix + "/" + strDate + "/" + strHour + "/" + segment_name;
|
||||||
if (isLive()) {
|
if ((!isKeep())) {
|
||||||
_segment_file_paths.emplace(index, segment_path);
|
_segment_file_paths.emplace(index, segment_path);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_file = makeFile(segment_path, true);
|
_file = makeFile(segment_path, true);
|
||||||
|
|
||||||
//保存本切片的元数据
|
//保存本切片的元数据
|
||||||
@ -93,11 +87,12 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
|||||||
|
|
||||||
if (!_file) {
|
if (!_file) {
|
||||||
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
if (_params.empty()) {
|
if (_params.empty()) {
|
||||||
return segment_name;
|
return strDate + "/" + strHour + "/" + segment_name;
|
||||||
}
|
}
|
||||||
return segment_name + "?" + _params;
|
return strDate + "/" + strHour + "/" + segment_name + "?" + _params;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMakerImp::onDelSegment(uint64_t index) {
|
void HlsMakerImp::onDelSegment(uint64_t index) {
|
||||||
@ -168,4 +163,8 @@ HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
|||||||
return _media_src;
|
return _media_src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string HlsMakerImp::getPathPrefix() {
|
||||||
|
return _path_prefix;
|
||||||
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
@ -8,8 +8,8 @@
|
|||||||
* may be found in the AUTHORS file in the root of the source tree.
|
* may be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef HLSMAKERIMP_H
|
#ifndef HLSMAKERIMPSUB_H
|
||||||
#define HLSMAKERIMP_H
|
#define HLSMAKERIMPSUB_H
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -21,7 +21,8 @@ namespace mediakit {
|
|||||||
|
|
||||||
class HlsMakerImp : public HlsMaker{
|
class HlsMakerImp : public HlsMaker{
|
||||||
public:
|
public:
|
||||||
HlsMakerImp(const std::string &m3u8_file,
|
HlsMakerImp(
|
||||||
|
const std::string &m3u8_file,
|
||||||
const std::string ¶ms,
|
const std::string ¶ms,
|
||||||
uint32_t bufSize = 64 * 1024,
|
uint32_t bufSize = 64 * 1024,
|
||||||
float seg_duration = 5,
|
float seg_duration = 5,
|
||||||
@ -55,6 +56,7 @@ protected:
|
|||||||
void onWriteSegment(const char *data, size_t len) override;
|
void onWriteSegment(const char *data, size_t len) override;
|
||||||
void onWriteHls(const std::string &data) override;
|
void onWriteHls(const std::string &data) override;
|
||||||
void onFlushLastSegment(uint64_t duration_ms) override;
|
void onFlushLastSegment(uint64_t duration_ms) override;
|
||||||
|
std::string getPathPrefix() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<FILE> makeFile(const std::string &file,bool setbuf = false);
|
std::shared_ptr<FILE> makeFile(const std::string &file,bool setbuf = false);
|
||||||
@ -69,9 +71,8 @@ private:
|
|||||||
std::shared_ptr<FILE> _file;
|
std::shared_ptr<FILE> _file;
|
||||||
std::shared_ptr<char> _file_buf;
|
std::shared_ptr<char> _file_buf;
|
||||||
HlsMediaSource::Ptr _media_src;
|
HlsMediaSource::Ptr _media_src;
|
||||||
toolkit::EventPoller::Ptr _poller;
|
|
||||||
std::map<uint64_t/*index*/,std::string/*file_path*/> _segment_file_paths;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
#endif //HLSMAKERIMP_H
|
#endif //HLSMAKERIMPSUB_H
|
||||||
|
|||||||
@ -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 ¶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<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
|
|
||||||
@ -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 ¶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<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
|
|
||||||
@ -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) {
|
|
||||||
//最小允许设置为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<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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user