添加hls 落盘录制

This commit is contained in:
monktan 2021-12-16 20:47:28 +08:00
parent b3dd440151
commit fddb6a13ca
14 changed files with 312 additions and 24 deletions

View File

@ -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
# 录制 hls 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的用户名密码
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
#rtsp播放是否开启专属鉴权事件置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权

View File

@ -43,6 +43,7 @@ const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader";
const string kOnHttpAccess = HOOK_FIELD"on_http_access";
const string kOnServerStarted = HOOK_FIELD"on_server_started";
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 kAliveInterval = HOOK_FIELD"alive_interval";
@ -64,6 +65,7 @@ onceToken token([](){
mINI::Instance()[kOnHttpAccess] = "";
mINI::Instance()[kOnServerStarted] = "";
mINI::Instance()[kOnServerKeepalive] = "";
mINI::Instance()[kOnRecordHls] = "";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
mINI::Instance()[kAliveInterval] = 30.0;
},nullptr);
@ -401,6 +403,20 @@ void installWebHook(){
});
#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) {
GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs);
if (!hook_enable || hook_record_ts.empty()) {

View File

@ -119,13 +119,16 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list
int MultiMediaSourceMuxer::totalReaderCount() const {
auto hls = _hls;
auto hls_disk = _hls_disk;
auto ret = (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) +
#if defined(ENABLE_MP4)
(_fmp4 ? _fmp4->readerCount() : 0) +
(_mp4 ? 1 : 0) +
#endif
(hls ? hls->readerCount() : 0);
(hls ? hls->readerCount() : 0) +
(hls_disk ? 1 : 0);
#if defined(ENABLE_RTPPROXY)
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 ret = false;
switch (type) {
case Recorder::type_hls : {
if (start && !_hls) {
@ -167,7 +171,8 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//停止录制
_hls = nullptr;
}
return true;
ret = true;
goto ret;
}
case Recorder::type_mp4 : {
if (start && !_mp4) {
@ -177,10 +182,28 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//停止录制
_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;
case Recorder::type_mp4 :
return !!_mp4;
case Recorder::type_hls_disk:
return !!_hls_disk;
default:
return false;
}
@ -272,10 +297,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
if (hls) {
ret = hls->addTrack(track) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
ret = mp4->addTrack(track) ? true : ret;
}
auto hls_disk = _hls_disk;
if (hls_disk) {
ret = hls_disk->addTrack(track) ? true : ret;
}
return ret;
}
@ -335,6 +367,11 @@ void MultiMediaSourceMuxer::resetTracks() {
if (mp4) {
mp4->resetTracks();
}
auto hls_disk = _hls_disk;
if (hls_disk) {
hls_disk->resetTracks();
}
}
//该类实现frame级别的时间戳覆盖
@ -414,11 +451,17 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
if (hls) {
ret = hls->inputFrame(frame) ? true : ret;
}
auto mp4 = _mp4;
if (mp4) {
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 (_fmp4) {
ret = _fmp4->inputFrame(frame) ? true : ret;

View File

@ -159,6 +159,7 @@ private:
TSMediaSourceMuxer::Ptr _ts;
MediaSinkInterface::Ptr _mp4;
HlsRecorder::Ptr _hls;
MediaSinkInterface::Ptr _hls_disk;
//对象个数统计
ObjectStatistic<MultiMediaSourceMuxer> _statistic;

View File

@ -53,6 +53,7 @@ const string kBroadcastShellLogin = "kBroadcastShellLogin";
const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream";
const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader";
const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess";
const string kBroadcastRecordHlsDisk = "kBroadcastRecordHlsDisk";
} //namespace Broadcast
//通用配置项目

View File

@ -45,6 +45,10 @@ extern const string kBroadcastRecordMP4;
extern const string kBroadcastRecordTs;
#define BroadcastRecordTsArgs const RecordInfo &info
//录制hls文件成功后广播
extern const string kBroadcastRecordHlsDisk;
#define BroadcastRecordHlsDiskArgs const RecordInfo &info
//收到http api请求广播
extern const string kBroadcastHttpRequest;
#define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,SockInfo &sender

View File

@ -9,18 +9,19 @@
*/
#include "HlsMaker.h"
namespace mediakit {
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number) {
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, Recorder::type type) {
//最小允许设置为00个切片代表点播
_seg_number = seg_number;
_seg_duration = seg_duration;
_type = type;
}
HlsMaker::~HlsMaker() {
}
void HlsMaker::makeIndexFile(bool eof) {
char file_content[1024];
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;
if (_seg_number == 0) {
@ -57,19 +58,32 @@ void HlsMaker::makeIndexFile(bool eof) {
}
m3u8.assign(file_content);
string rm3u8 = m3u8;
string rcontent;
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());
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());
rcontent.assign(file_content);
}
if (eof) {
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
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) {
if (data && len) {
@ -89,7 +103,7 @@ void HlsMaker::inputData(void *data, size_t len, uint32_t timestamp, bool is_idr
}
void HlsMaker::delOldSegment() {
if (_seg_number == 0) {
if (_seg_number == 0 || _type == Recorder::type_hls_disk) {
//如果设置为保留0个切片则认为是保存为点播
return;
}
@ -112,7 +126,7 @@ void HlsMaker::addNewSegment(uint32_t stamp) {
}
//关闭并保存上一个切片如果_seg_number==0,那么是点播。
flushLastSegment(false);
flushLastSegment(_seg_number == 0 || _type == Recorder::type_hls_disk);
//新增切片
_last_file_name = onOpenSegment(_file_index++);
//记录本次切片的起始时间戳
@ -129,7 +143,7 @@ void HlsMaker::flushLastSegment(bool eof){
if (seg_dur <= 0) {
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();
delOldSegment();
makeIndexFile(eof);
@ -137,7 +151,7 @@ void HlsMaker::flushLastSegment(bool eof){
}
bool HlsMaker::isLive() {
return _seg_number != 0;
return _seg_number != 0 && _type == Recorder::type_hls;
}
void HlsMaker::clear() {

View File

@ -18,6 +18,8 @@
#include "Util/File.h"
#include "Util/util.h"
#include "Util/logger.h"
#include "Recorder.h"
using namespace toolkit;
namespace mediakit {
@ -28,7 +30,7 @@ public:
* @param seg_duration
* @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();
/**
@ -78,6 +80,12 @@ protected:
*/
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 ,
* @param duration_ms ts ,
@ -116,6 +124,7 @@ private:
uint64_t _file_index = 0;
string _last_file_name;
std::deque<tuple<int,string> > _seg_dur_list;
Recorder::type _type{Recorder::type_hls};
};
}//namespace mediakit

View File

@ -22,7 +22,8 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
const string &params,
uint32_t bufSize,
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();
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
_path_hls = m3u8_file;
@ -33,16 +34,37 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
});
_info.folder = _path_prefix;
_start_time = ::time(nullptr);
_type = type;
}
HlsMakerImp::~HlsMakerImp() {
clearCache(false);
clearCache(false, false);
}
void HlsMakerImp::clearCache(bool immediately) {
void HlsMakerImp::clearCache(bool immediately, bool first) {
//录制完了
flushLastSegment(true);
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;
}
@ -115,13 +137,13 @@ void HlsMakerImp::onWriteHls(const char *data, size_t len) {
if (hls) {
fwrite(data, len, 1, hls.get());
hls.reset();
if (_media_src) {
// 只有直播才注册
if (_media_src && _type == Recorder::type_hls) {
_media_src->registHls(true);
}
} else {
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
}
//DebugL << "\r\n" << string(data,len);
}
void HlsMakerImp::onFlushLastSegment(uint32_t duration_ms) {
@ -161,4 +183,42 @@ HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
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

View File

@ -27,7 +27,8 @@ public:
const string &params,
uint32_t bufSize = 64 * 1024,
float seg_duration = 5,
uint32_t seg_number = 3);
uint32_t seg_number = 3,
Recorder::type type = Recorder::type_hls);
~HlsMakerImp() override;
@ -49,17 +50,21 @@ public:
*
* @param immediately
*/
void clearCache(bool immediately = true);
void clearCache(bool immediately = true, bool first = false);
protected:
string onOpenSegment(uint64_t index) override ;
void onDelSegment(uint64_t index) override;
void onWriteSegment(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;
private:
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:
int _buf_size;
@ -72,6 +77,9 @@ private:
HlsMediaSource::Ptr _media_src;
EventPoller::Ptr _poller;
map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths;
time_t _start_time {0};
Recorder::type _type{Recorder::type_hls};
};
}//namespace mediakit

View 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

View 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 &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

View File

@ -13,6 +13,7 @@
#include "Common/MediaSource.h"
#include "MP4Recorder.h"
#include "HlsRecorder.h"
#include "HlsRecorderDisk.h"
using namespace toolkit;
@ -50,6 +51,21 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
}
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:
return "";
}
@ -77,6 +93,16 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
throw std::invalid_argument("mp4相关功能未打开请开启ENABLE_MP4宏后编译再测试");
#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("未知的录制类型");
}

View File

@ -36,7 +36,9 @@ public:
// 录制hls
type_hls = 0,
// 录制MP4
type_mp4 = 1
type_mp4 = 1,
// 录制hls落盘
type_hls_disk = 2,
} type;
/**