mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-25 20:27:34 +08:00
添加hls 落盘录制
This commit is contained in:
parent
b3dd440151
commit
fddb6a13ca
@ -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";
|
|||||||
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([](){
|
|||||||
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(){
|
|||||||
});
|
});
|
||||||
#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
|
|||||||
|
|
||||||
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) {
|
|||||||
|
|
||||||
//此函数可能跨线程调用
|
//此函数可能跨线程调用
|
||||||
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
|
|||||||
//停止录制
|
//停止录制
|
||||||
_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
|
|||||||
//停止录制
|
//停止录制
|
||||||
_mp4 = nullptr;
|
_mp4 = nullptr;
|
||||||
}
|
}
|
||||||
return true;
|
ret = true;
|
||||||
|
goto ret;
|
||||||
}
|
}
|
||||||
default : return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret:
|
||||||
|
MediaSourceEventInterceptor::onReaderChanged(sender, totalReaderCount());
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
//此函数可能跨线程调用
|
//此函数可能跨线程调用
|
||||||
@ -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) {
|
|||||||
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() {
|
|||||||
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) {
|
|||||||
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:
|
|||||||
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";
|
|||||||
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;
|
|||||||
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 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m3u8.assign(file_content);
|
m3u8.assign(file_content);
|
||||||
|
string rm3u8 = m3u8;
|
||||||
|
string rcontent;
|
||||||
|
|
||||||
|
if (_type == Recorder::type_hls) {;
|
||||||
for (auto &tp : _seg_dur_list) {
|
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());
|
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);
|
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());
|
||||||
|
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//关闭并保存上一个切片,如果_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){
|
|||||||
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){
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 @@
|
|||||||
#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:
|
|||||||
* @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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,6 +80,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:
|
|||||||
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,
|
|||||||
const string ¶ms,
|
const string ¶ms,
|
||||||
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,
|
|||||||
});
|
});
|
||||||
|
|
||||||
_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) {
|
|||||||
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 {
|
|||||||
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
|
@ -27,7 +27,8 @@ public:
|
|||||||
const string ¶ms,
|
const string ¶ms,
|
||||||
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:
|
|||||||
* 清空缓存
|
* 清空缓存
|
||||||
* @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:
|
|||||||
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
|
||||||
|
25
src/Record/HlsRecorderDisk.cpp
Normal file
25
src/Record/HlsRecorderDisk.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
77
src/Record/HlsRecorderDisk.h
Normal file
77
src/Record/HlsRecorderDisk.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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 ¶ms){
|
||||||
|
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 @@
|
|||||||
#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
|
|||||||
}
|
}
|
||||||
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
|
|||||||
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:
|
|||||||
// 录制hls
|
// 录制hls
|
||||||
type_hls = 0,
|
type_hls = 0,
|
||||||
// 录制MP4
|
// 录制MP4
|
||||||
type_mp4 = 1
|
type_mp4 = 1,
|
||||||
|
// 录制hls落盘
|
||||||
|
type_hls_disk = 2,
|
||||||
} type;
|
} type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user