Commit fddb6a13 by monktan

添加hls 落盘录制

parent b3dd4401
...@@ -124,6 +124,8 @@ on_publish=https://127.0.0.1/index/hook/on_publish ...@@ -124,6 +124,8 @@ on_publish=https://127.0.0.1/index/hook/on_publish
on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
# 录制 hls ts 切片完成事件 # 录制 hls ts 切片完成事件
on_record_ts=https://127.0.0.1/index/hook/on_record_ts on_record_ts=https://127.0.0.1/index/hook/on_record_ts
#录制hls切片完成事件,http api接口录制类型为2
on_record_hls=http://127.0.0.1/index/hook/on_record_hls
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
#rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权 #rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
......
...@@ -43,6 +43,7 @@ const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader"; ...@@ -43,6 +43,7 @@ const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
const string kOnHttpAccess = HOOK_FIELD"on_http_access"; const string kOnHttpAccess = HOOK_FIELD"on_http_access";
const string kOnServerStarted = HOOK_FIELD"on_server_started"; const string kOnServerStarted = HOOK_FIELD"on_server_started";
const string kOnServerKeepalive = HOOK_FIELD"on_server_keepalive"; const string kOnServerKeepalive = HOOK_FIELD"on_server_keepalive";
const string kOnRecordHls = HOOK_FIELD"on_record_hls";
const string kAdminParams = HOOK_FIELD"admin_params"; const string kAdminParams = HOOK_FIELD"admin_params";
const string kAliveInterval = HOOK_FIELD"alive_interval"; const string kAliveInterval = HOOK_FIELD"alive_interval";
...@@ -64,6 +65,7 @@ onceToken token([](){ ...@@ -64,6 +65,7 @@ onceToken token([](){
mINI::Instance()[kOnHttpAccess] = ""; mINI::Instance()[kOnHttpAccess] = "";
mINI::Instance()[kOnServerStarted] = ""; mINI::Instance()[kOnServerStarted] = "";
mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnServerKeepalive] = "";
mINI::Instance()[kOnRecordHls] = "";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kAliveInterval] = 30.0;
},nullptr); },nullptr);
...@@ -401,6 +403,20 @@ void installWebHook(){ ...@@ -401,6 +403,20 @@ void installWebHook(){
}); });
#endif //ENABLE_MP4 #endif //ENABLE_MP4
#ifdef ENABLE_HLS
//录制hls文件落盘成功后广播
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordHlsDisk,[](BroadcastRecordHlsDiskArgs){
GET_CONFIG(bool, hook_enable, Hook::kEnable)
GET_CONFIG(string, hook_record_hls, Hook::kOnRecordHls);
if(!hook_enable || hook_record_hls.empty()){
return;
}
//执行hook
do_http_hook(hook_record_hls, getRecordInfo(info), nullptr);
});
#endif
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastRecordTs, [](BroadcastRecordTsArgs) { NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastRecordTs, [](BroadcastRecordTsArgs) {
GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs); GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs);
if (!hook_enable || hook_record_ts.empty()) { if (!hook_enable || hook_record_ts.empty()) {
......
...@@ -119,13 +119,16 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list ...@@ -119,13 +119,16 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list
int MultiMediaSourceMuxer::totalReaderCount() const { int MultiMediaSourceMuxer::totalReaderCount() const {
auto hls = _hls; auto hls = _hls;
auto hls_disk = _hls_disk;
auto ret = (_rtsp ? _rtsp->readerCount() : 0) + auto ret = (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) +
#if defined(ENABLE_MP4) #if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->readerCount() : 0) + (_fmp4 ? _fmp4->readerCount() : 0) +
(_mp4 ? 1 : 0) +
#endif #endif
(hls ? hls->readerCount() : 0); (hls ? hls->readerCount() : 0) +
(hls_disk ? 1 : 0);
#if defined(ENABLE_RTPPROXY) #if defined(ENABLE_RTPPROXY)
return ret + (int)_rtp_sender.size(); return ret + (int)_rtp_sender.size();
...@@ -153,6 +156,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { ...@@ -153,6 +156,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
//此函数可能跨线程调用 //此函数可能跨线程调用
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
bool ret = false;
switch (type) { switch (type) {
case Recorder::type_hls : { case Recorder::type_hls : {
if (start && !_hls) { if (start && !_hls) {
...@@ -167,7 +171,8 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type ...@@ -167,7 +171,8 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//停止录制 //停止录制
_hls = nullptr; _hls = nullptr;
} }
return true; ret = true;
goto ret;
} }
case Recorder::type_mp4 : { case Recorder::type_mp4 : {
if (start && !_mp4) { if (start && !_mp4) {
...@@ -177,10 +182,28 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type ...@@ -177,10 +182,28 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//停止录制 //停止录制
_mp4 = nullptr; _mp4 = nullptr;
} }
return true; ret = true;
goto ret;
}
case Recorder::type_hls_disk: {
if (start && !_hls_disk) {
// 开始录制
_hls_disk = makeRecorder(sender, getTracks(), type, custom_path, max_second);
} else if (!start && _hls_disk) {
// 停止录制
_hls_disk = nullptr;
}
ret = true;
goto ret;
}
default : {
ret = false;
goto ret;
} }
default : return false;
} }
ret:
MediaSourceEventInterceptor::onReaderChanged(sender, totalReaderCount());
return ret;
} }
//此函数可能跨线程调用 //此函数可能跨线程调用
...@@ -190,6 +213,8 @@ bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type ...@@ -190,6 +213,8 @@ bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type
return !!_hls; return !!_hls;
case Recorder::type_mp4 : case Recorder::type_mp4 :
return !!_mp4; return !!_mp4;
case Recorder::type_hls_disk:
return !!_hls_disk;
default: default:
return false; return false;
} }
...@@ -272,10 +297,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { ...@@ -272,10 +297,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
if (hls) { if (hls) {
ret = hls->addTrack(track) ? true : ret; ret = hls->addTrack(track) ? true : ret;
} }
auto mp4 = _mp4; auto mp4 = _mp4;
if (mp4) { if (mp4) {
ret = mp4->addTrack(track) ? true : ret; ret = mp4->addTrack(track) ? true : ret;
} }
auto hls_disk = _hls_disk;
if (hls_disk) {
ret = hls_disk->addTrack(track) ? true : ret;
}
return ret; return ret;
} }
...@@ -335,6 +367,11 @@ void MultiMediaSourceMuxer::resetTracks() { ...@@ -335,6 +367,11 @@ void MultiMediaSourceMuxer::resetTracks() {
if (mp4) { if (mp4) {
mp4->resetTracks(); mp4->resetTracks();
} }
auto hls_disk = _hls_disk;
if (hls_disk) {
hls_disk->resetTracks();
}
} }
//该类实现frame级别的时间戳覆盖 //该类实现frame级别的时间戳覆盖
...@@ -414,11 +451,17 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { ...@@ -414,11 +451,17 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
if (hls) { if (hls) {
ret = hls->inputFrame(frame) ? true : ret; ret = hls->inputFrame(frame) ? true : ret;
} }
auto mp4 = _mp4; auto mp4 = _mp4;
if (mp4) { if (mp4) {
ret = mp4->inputFrame(frame) ? true : ret; ret = mp4->inputFrame(frame) ? true : ret;
} }
auto hls_disk = _hls_disk;
if (hls_disk) {
ret = hls_disk->inputFrame(frame) ? true : ret;
}
#if defined(ENABLE_MP4) #if defined(ENABLE_MP4)
if (_fmp4) { if (_fmp4) {
ret = _fmp4->inputFrame(frame) ? true : ret; ret = _fmp4->inputFrame(frame) ? true : ret;
......
...@@ -159,6 +159,7 @@ private: ...@@ -159,6 +159,7 @@ private:
TSMediaSourceMuxer::Ptr _ts; TSMediaSourceMuxer::Ptr _ts;
MediaSinkInterface::Ptr _mp4; MediaSinkInterface::Ptr _mp4;
HlsRecorder::Ptr _hls; HlsRecorder::Ptr _hls;
MediaSinkInterface::Ptr _hls_disk;
//对象个数统计 //对象个数统计
ObjectStatistic<MultiMediaSourceMuxer> _statistic; ObjectStatistic<MultiMediaSourceMuxer> _statistic;
......
...@@ -53,6 +53,7 @@ const string kBroadcastShellLogin = "kBroadcastShellLogin"; ...@@ -53,6 +53,7 @@ const string kBroadcastShellLogin = "kBroadcastShellLogin";
const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream"; const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream";
const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader"; const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader";
const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess"; const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess";
const string kBroadcastRecordHlsDisk = "kBroadcastRecordHlsDisk";
} //namespace Broadcast } //namespace Broadcast
//通用配置项目 //通用配置项目
......
...@@ -45,6 +45,10 @@ extern const string kBroadcastRecordMP4; ...@@ -45,6 +45,10 @@ extern const string kBroadcastRecordMP4;
extern const string kBroadcastRecordTs; extern const string kBroadcastRecordTs;
#define BroadcastRecordTsArgs const RecordInfo &info #define BroadcastRecordTsArgs const RecordInfo &info
//录制hls文件成功后广播
extern const string kBroadcastRecordHlsDisk;
#define BroadcastRecordHlsDiskArgs const RecordInfo &info
//收到http api请求广播 //收到http api请求广播
extern const string kBroadcastHttpRequest; extern const string kBroadcastHttpRequest;
#define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,SockInfo &sender #define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,SockInfo &sender
......
...@@ -9,18 +9,19 @@ ...@@ -9,18 +9,19 @@
*/ */
#include "HlsMaker.h" #include "HlsMaker.h"
namespace mediakit { namespace mediakit {
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number) { HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, Recorder::type type) {
//最小允许设置为0,0个切片代表点播 //最小允许设置为0,0个切片代表点播
_seg_number = seg_number; _seg_number = seg_number;
_seg_duration = seg_duration; _seg_duration = seg_duration;
_type = type;
} }
HlsMaker::~HlsMaker() { HlsMaker::~HlsMaker() {
} }
void HlsMaker::makeIndexFile(bool eof) { void HlsMaker::makeIndexFile(bool eof) {
char file_content[1024]; char file_content[1024];
int maxSegmentDuration = 0; int maxSegmentDuration = 0;
...@@ -32,7 +33,7 @@ void HlsMaker::makeIndexFile(bool eof) { ...@@ -32,7 +33,7 @@ void HlsMaker::makeIndexFile(bool eof) {
} }
} }
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; auto sequence = _type == Recorder::type_hls ? ( _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL): 0LL;
string m3u8; string m3u8;
if (_seg_number == 0) { if (_seg_number == 0) {
...@@ -57,19 +58,32 @@ void HlsMaker::makeIndexFile(bool eof) { ...@@ -57,19 +58,32 @@ void HlsMaker::makeIndexFile(bool eof) {
} }
m3u8.assign(file_content); m3u8.assign(file_content);
string rm3u8 = m3u8;
string rcontent;
for (auto &tp : _seg_dur_list) { if (_type == Recorder::type_hls) {;
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);
}
} else if (_type == Recorder::type_hls_disk) {
auto &tp = _seg_dur_list.back();
snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data()); 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); rcontent.assign(file_content);
} }
if (eof) { if (eof) {
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
m3u8.append(file_content); m3u8.append(file_content);
rcontent.append(file_content);
} }
onWriteHls(m3u8.data(), m3u8.size());
}
if (_type == Recorder::type_hls) {
onWriteHls(m3u8.data(), m3u8.size());
} else if (_type == Recorder::type_hls_disk) {
onWriteRecordM3u8(rm3u8.data(), rm3u8.size(), rcontent.data(), rcontent.size());
}
}
void HlsMaker::inputData(void *data, size_t len, uint32_t timestamp, bool is_idr_fast_packet) { void HlsMaker::inputData(void *data, size_t len, uint32_t timestamp, bool is_idr_fast_packet) {
if (data && len) { if (data && len) {
...@@ -89,7 +103,7 @@ void HlsMaker::inputData(void *data, size_t len, uint32_t timestamp, bool is_idr ...@@ -89,7 +103,7 @@ void HlsMaker::inputData(void *data, size_t len, uint32_t timestamp, bool is_idr
} }
void HlsMaker::delOldSegment() { void HlsMaker::delOldSegment() {
if (_seg_number == 0) { if (_seg_number == 0 || _type == Recorder::type_hls_disk) {
//如果设置为保留0个切片,则认为是保存为点播 //如果设置为保留0个切片,则认为是保存为点播
return; return;
} }
...@@ -112,7 +126,7 @@ void HlsMaker::addNewSegment(uint32_t stamp) { ...@@ -112,7 +126,7 @@ void HlsMaker::addNewSegment(uint32_t stamp) {
} }
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。 //关闭并保存上一个切片,如果_seg_number==0,那么是点播。
flushLastSegment(false); flushLastSegment(_seg_number == 0 || _type == Recorder::type_hls_disk);
//新增切片 //新增切片
_last_file_name = onOpenSegment(_file_index++); _last_file_name = onOpenSegment(_file_index++);
//记录本次切片的起始时间戳 //记录本次切片的起始时间戳
...@@ -129,7 +143,7 @@ void HlsMaker::flushLastSegment(bool eof){ ...@@ -129,7 +143,7 @@ void HlsMaker::flushLastSegment(bool eof){
if (seg_dur <= 0) { if (seg_dur <= 0) {
seg_dur = 100; seg_dur = 100;
} }
_seg_dur_list.push_back(std::make_tuple(seg_dur, std::move(_last_file_name))); _seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name));
_last_file_name.clear(); _last_file_name.clear();
delOldSegment(); delOldSegment();
makeIndexFile(eof); makeIndexFile(eof);
...@@ -137,7 +151,7 @@ void HlsMaker::flushLastSegment(bool eof){ ...@@ -137,7 +151,7 @@ void HlsMaker::flushLastSegment(bool eof){
} }
bool HlsMaker::isLive() { bool HlsMaker::isLive() {
return _seg_number != 0; return _seg_number != 0 && _type == Recorder::type_hls;
} }
void HlsMaker::clear() { void HlsMaker::clear() {
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include "Util/File.h" #include "Util/File.h"
#include "Util/util.h" #include "Util/util.h"
#include "Util/logger.h" #include "Util/logger.h"
#include "Recorder.h"
using namespace toolkit; using namespace toolkit;
namespace mediakit { namespace mediakit {
...@@ -28,7 +30,7 @@ public: ...@@ -28,7 +30,7 @@ public:
* @param seg_duration 切片文件长度 * @param seg_duration 切片文件长度
* @param seg_number 切片个数 * @param seg_number 切片个数
*/ */
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3); HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, Recorder::type type = Recorder::type_hls);
virtual ~HlsMaker(); virtual ~HlsMaker();
/** /**
...@@ -79,6 +81,12 @@ protected: ...@@ -79,6 +81,12 @@ protected:
virtual void onWriteHls(const char *data, size_t len) = 0; virtual void onWriteHls(const char *data, size_t len) = 0;
/** /**
* 写m3u8文件回调,hls落盘使用
* @param data
* @param len
*/
virtual void onWriteRecordM3u8(const char *header, size_t hlen, const char *body, size_t blen) = 0;
/**
* 上一个 ts 切片写入完成, 可在这里进行通知处理 * 上一个 ts 切片写入完成, 可在这里进行通知处理
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒 * @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
*/ */
...@@ -116,6 +124,7 @@ private: ...@@ -116,6 +124,7 @@ private:
uint64_t _file_index = 0; uint64_t _file_index = 0;
string _last_file_name; string _last_file_name;
std::deque<tuple<int,string> > _seg_dur_list; std::deque<tuple<int,string> > _seg_dur_list;
Recorder::type _type{Recorder::type_hls};
}; };
}//namespace mediakit }//namespace mediakit
......
...@@ -22,7 +22,8 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file, ...@@ -22,7 +22,8 @@ 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) : HlsMaker(seg_duration, seg_number) { uint32_t seg_number,
Recorder::type type) : HlsMaker(seg_duration, seg_number, type) {
_poller = EventPollerPool::Instance().getPoller(); _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;
...@@ -33,16 +34,37 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file, ...@@ -33,16 +34,37 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
}); });
_info.folder = _path_prefix; _info.folder = _path_prefix;
_start_time = ::time(nullptr);
_type = type;
} }
HlsMakerImp::~HlsMakerImp() { HlsMakerImp::~HlsMakerImp() {
clearCache(false); clearCache(false, false);
} }
void HlsMakerImp::clearCache(bool immediately) { void HlsMakerImp::clearCache(bool immediately, bool first) {
//录制完了 //录制完了
flushLastSegment(true); flushLastSegment(true);
if (!isLive()) { if (!isLive()) {
if (first) return; //第一次创建清除cache不需要上报
//hook接口,hls落盘录制,触发hook
auto info = _info;
if (_media_src) {
info.app = _media_src.get()->getApp();
info.stream = _media_src.get()->getId();
info.vhost = _media_src.get()->getVhost();
info.file_path = _path_hls;
info.start_time = _start_time;
info.time_len = ::time(nullptr) - _start_time;
info.folder = _info.folder;
info.file_name = _path_hls;
info.url = _path_hls;
info.file_size = 0;
}
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordHlsDisk, info);
return; return;
} }
...@@ -115,13 +137,13 @@ void HlsMakerImp::onWriteHls(const char *data, size_t len) { ...@@ -115,13 +137,13 @@ void HlsMakerImp::onWriteHls(const char *data, size_t len) {
if (hls) { if (hls) {
fwrite(data, len, 1, hls.get()); fwrite(data, len, 1, hls.get());
hls.reset(); hls.reset();
if (_media_src) { // 只有直播才注册
if (_media_src && _type == Recorder::type_hls) {
_media_src->registHls(true); _media_src->registHls(true);
} }
} else { } else {
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
} }
//DebugL << "\r\n" << string(data,len);
} }
void HlsMakerImp::onFlushLastSegment(uint32_t duration_ms) { void HlsMakerImp::onFlushLastSegment(uint32_t duration_ms) {
...@@ -161,4 +183,42 @@ HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { ...@@ -161,4 +183,42 @@ HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
return _media_src; return _media_src;
} }
void HlsMakerImp::onWriteRecordM3u8(const char *header, size_t hlen, const char *body, size_t blen) {
bool exist = true;
string mode = "rb+";
if (access(_path_hls.c_str(), 0) == -1) {
exist = false;
mode = "wb+";
}
auto hls_file = makeRecordM3u8(_path_hls, mode);
if (hls_file) {
fwrite(header, hlen, 1, hls_file.get());
if (exist) {
fseek(hls_file.get(), -15L, SEEK_END);
}
fwrite(body, blen,1, hls_file.get());
hls_file.reset();
if(_media_src && _type == Recorder::type_hls){
_media_src->registHls(true);
}
} else {
WarnL << "create hls_file file failed, " << _path_hls << " " << get_uv_errmsg();
}
}
std::shared_ptr<FILE> HlsMakerImp::makeRecordM3u8(const string &file, const string &mode, bool setbuf) {
auto file_buf = _file_buf;
auto ret= shared_ptr<FILE>(File::create_file(file.data(), mode.data()), [file_buf](FILE *fp) {
if (fp) {
fclose(fp);
}
});
if (ret && setbuf) {
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
}
return ret;
}
}//namespace mediakit }//namespace mediakit
\ No newline at end of file
...@@ -27,7 +27,8 @@ public: ...@@ -27,7 +27,8 @@ public:
const string &params, const string &params,
uint32_t bufSize = 64 * 1024, uint32_t bufSize = 64 * 1024,
float seg_duration = 5, float seg_duration = 5,
uint32_t seg_number = 3); uint32_t seg_number = 3,
Recorder::type type = Recorder::type_hls);
~HlsMakerImp() override; ~HlsMakerImp() override;
...@@ -49,17 +50,21 @@ public: ...@@ -49,17 +50,21 @@ public:
* 清空缓存 * 清空缓存
* @param immediately 时候立即删除 * @param immediately 时候立即删除
*/ */
void clearCache(bool immediately = true); void clearCache(bool immediately = true, bool first = false);
protected: protected:
string onOpenSegment(uint64_t index) override ; string onOpenSegment(uint64_t index) override ;
void onDelSegment(uint64_t index) override; void onDelSegment(uint64_t index) override;
void onWriteSegment(const char *data, size_t len) override; void onWriteSegment(const char *data, size_t len) override;
void onWriteHls(const char *data, size_t len) override; void onWriteHls(const char *data, size_t len) override;
// hls 落盘使用
void onWriteRecordM3u8(const char *header, size_t hlen, const char *body, size_t blen) override;
void onFlushLastSegment(uint32_t duration_ms) override; void onFlushLastSegment(uint32_t duration_ms) override;
private: private:
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false); std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
// hls 落盘使用
std::shared_ptr<FILE> makeRecordM3u8(const string &file, const string &mode, bool setbuf = false);
private: private:
int _buf_size; int _buf_size;
...@@ -72,6 +77,9 @@ private: ...@@ -72,6 +77,9 @@ private:
HlsMediaSource::Ptr _media_src; HlsMediaSource::Ptr _media_src;
EventPoller::Ptr _poller; EventPoller::Ptr _poller;
map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths; map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths;
time_t _start_time {0};
Recorder::type _type{Recorder::type_hls};
}; };
}//namespace mediakit }//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 "HlsRecorderDisk.h"
namespace mediakit {
void HlsRecorderDisk::resetTracks() {
TsMuxer::resetTracks();
}
bool HlsRecorderDisk::addTrack(const Track::Ptr & track) {
return TsMuxer::addTrack(track);
}
}//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.
*/
#ifndef HLSRECORDER_DISK_H
#define HLSRECORDER_DISK_H
#include "Common/MediaSink.h"
#include "HlsMakerImp.h"
#include "TsMuxer.h"
namespace mediakit {
class HlsRecorderDisk : public TsMuxer, public std::enable_shared_from_this<HlsRecorderDisk> {
public:
typedef std::shared_ptr<HlsRecorderDisk> Ptr;
HlsRecorderDisk(const string &m3u8_file, const string &params){
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration,
hlsNum, Recorder::type_hls_disk);
//清空上次的残余文件
_hls->clearCache(false, true);
}
~HlsRecorderDisk() override= default;
/**
* 重置所有Track
*/
void resetTracks() override;
/**
* 输入frame
*/
//bool inputFrame(const Frame::Ptr &frame) override;
/**
* 添加ready状态的track
*/
bool addTrack(const Track::Ptr & track) override;
void setMediaSource(const string &vhost, const string &app, const string &stream_id) {
_hls->setMediaSource(vhost, app, stream_id);
}
bool inputFrame(const Frame::Ptr &frame) override {;
if (_clear_cache) {
_clear_cache = false;
_hls->clearCache();
}
return TsMuxer::inputFrame(frame);
}
private:
void onTs(std::shared_ptr<Buffer> buffer, uint32_t timestamp, bool is_idr_fast_packet) override {
if (!buffer) {
_hls->inputData(nullptr, 0, timestamp, is_idr_fast_packet);
} else {
_hls->inputData(buffer->data(), buffer->size(), timestamp, is_idr_fast_packet);
}
}
private:
bool _clear_cache = false;
std::shared_ptr<HlsMakerImp> _hls;
};
}//namespace mediakit
#endif //HLSRECORDER_DISK_H
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "Common/MediaSource.h" #include "Common/MediaSource.h"
#include "MP4Recorder.h" #include "MP4Recorder.h"
#include "HlsRecorder.h" #include "HlsRecorder.h"
#include "HlsRecorderDisk.h"
using namespace toolkit; using namespace toolkit;
...@@ -50,6 +51,21 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s ...@@ -50,6 +51,21 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
} }
return File::absolutePath(mp4FilePath, recordPath); return File::absolutePath(mp4FilePath, recordPath);
} }
case Recorder::type_hls_disk: {
GET_CONFIG(string, hlsPath, Record::kFilePath);
string m3u8FilePath;
if (enableVhost) {
m3u8FilePath = "hls_record/" + vhost + "/" + app + "/" + stream_id + "/record.m3u8";
} else {
m3u8FilePath = "hls_record/" + app + "/" + stream_id + "/record.m3u8";
}
//Here we use the customized file path.
if (!customized_path.empty()) {
m3u8FilePath = "/" + stream_id + "/record.m3u8";
return File::absolutePath(m3u8FilePath, customized_path);
}
return File::absolutePath(m3u8FilePath, hlsPath);
}
default: default:
return ""; return "";
} }
...@@ -77,6 +93,16 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st ...@@ -77,6 +93,16 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试");
#endif #endif
} }
case Recorder::type_hls_disk: {
#if defined(ENABLE_HLS)
GET_CONFIG(bool, enable_vhost, General::kEnableVhost);
auto ret = std::make_shared<HlsRecorderDisk>(path, enable_vhost ? string(VHOST_KEY) + "=" + vhost : "");
ret->setMediaSource(vhost, app, stream_id);
return ret;
#else
throw std::invalid_argument("hls相关功能未打开,请开启ENABLE_HLS宏后编译再测试");
#endif
}
default: throw std::invalid_argument("未知的录制类型"); default: throw std::invalid_argument("未知的录制类型");
} }
......
...@@ -36,7 +36,9 @@ public: ...@@ -36,7 +36,9 @@ public:
// 录制hls // 录制hls
type_hls = 0, type_hls = 0,
// 录制MP4 // 录制MP4
type_mp4 = 1 type_mp4 = 1,
// 录制hls落盘
type_hls_disk = 2,
} type; } type;
/** /**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论