mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-23 01:57:50 +08:00
commit
338ddaf3b9
@ -94,6 +94,7 @@ maxStreamWaitMS=15000
|
||||
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
|
||||
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
|
||||
streamNoneReaderDelayMS=20000
|
||||
noRecordStreamNoneReaderDelayMS=130000
|
||||
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
resetWhenRePlay=1
|
||||
|
||||
@ -662,32 +662,73 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
|
||||
//没有任何人观看该视频源,表明该源可以关闭了
|
||||
GET_CONFIG(string, record_app, Record::kAppName);
|
||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||
GET_CONFIG(int, no_record_stream_none_reader_delay, General::kNoRecordStreamNoneReaderDelayMS);
|
||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
||||
bool is_mp4_vod = sender.getApp() == record_app;
|
||||
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
|
||||
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() {
|
||||
auto strong_sender = weak_sender.lock();
|
||||
if (!strong_sender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
if(sender.isRecording(Recorder::type_hls)) {//如果正在录像
|
||||
WarnL << "************The stream is Recording.*************";
|
||||
WarnL << sender.getUrl();
|
||||
_async_close_timer = std::make_shared<Timer>(
|
||||
stream_none_reader_delay / 1000.0f,
|
||||
[weak_sender, is_mp4_vod]() {
|
||||
auto strong_sender = weak_sender.lock();
|
||||
if (!strong_sender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strong_sender->totalReaderCount()) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return false;
|
||||
}
|
||||
if (strong_sender->totalReaderCount()) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
|
||||
strong_sender->close(false);
|
||||
}
|
||||
return false;
|
||||
}, nullptr);
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
|
||||
strong_sender->close(false);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nullptr);
|
||||
} else {//没有录像的话
|
||||
WarnL << "************The stream is Not Recording.*************";
|
||||
WarnL << sender.getUrl();
|
||||
_async_close_timer = std::make_shared<Timer>(
|
||||
no_record_stream_none_reader_delay / 1000.0f,
|
||||
[weak_sender, is_mp4_vod]() {
|
||||
auto strong_sender = weak_sender.lock();
|
||||
|
||||
if (strong_sender->isRecording(Recorder::type_hls)) {
|
||||
return false;
|
||||
}
|
||||
if (!strong_sender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strong_sender->totalReaderCount()) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
|
||||
strong_sender->close(false);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string MediaSourceEvent::getOriginUrl(MediaSource &sender) const {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
@ -192,8 +192,9 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||
});
|
||||
switch (type) {
|
||||
case Recorder::type_hls : {
|
||||
if (start && !_hls) {
|
||||
//开始录制
|
||||
if (!_hls)
|
||||
{
|
||||
//创建hls对象
|
||||
_option.hls_save_path = custom_path;
|
||||
auto hls = dynamic_pointer_cast<HlsRecorder>(makeRecorder(sender, getTracks(), type, _option));
|
||||
if (hls) {
|
||||
@ -201,10 +202,8 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||
hls->setListener(shared_from_this());
|
||||
}
|
||||
_hls = hls;
|
||||
} else if (!start && _hls) {
|
||||
//停止录制
|
||||
_hls = nullptr;
|
||||
}
|
||||
_hls->startRecord(start);
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_mp4 : {
|
||||
@ -227,7 +226,12 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
switch (type){
|
||||
case Recorder::type_hls :
|
||||
return !!_hls;
|
||||
//return !!_hls;
|
||||
if (_hls){
|
||||
return _hls->getRecordFlag();
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
case Recorder::type_mp4 :
|
||||
return !!_mp4;
|
||||
default:
|
||||
|
||||
@ -66,6 +66,7 @@ namespace General {
|
||||
const string kMediaServerId = GENERAL_FIELD "mediaServerId";
|
||||
const string kFlowThreshold = GENERAL_FIELD "flowThreshold";
|
||||
const string kStreamNoneReaderDelayMS = GENERAL_FIELD "streamNoneReaderDelayMS";
|
||||
const string kNoRecordStreamNoneReaderDelayMS = GENERAL_FIELD "noRecordStreamNoneReaderDelayMS";
|
||||
const string kMaxStreamWaitTimeMS = GENERAL_FIELD "maxStreamWaitMS";
|
||||
const string kEnableVhost = GENERAL_FIELD "enableVhost";
|
||||
const string kResetWhenRePlay = GENERAL_FIELD "resetWhenRePlay";
|
||||
@ -79,6 +80,7 @@ const string kUnreadyFrameCache = GENERAL_FIELD "unready_frame_cache";
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kFlowThreshold] = 1024;
|
||||
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
||||
mINI::Instance()[kNoRecordStreamNoneReaderDelayMS] = 130 * 1000;
|
||||
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
||||
mINI::Instance()[kEnableVhost] = 0;
|
||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||
|
||||
@ -158,6 +158,7 @@ extern const std::string kFlowThreshold;
|
||||
// 流无人观看并且超过若干时间后才触发kBroadcastStreamNoneReader事件
|
||||
// 默认连续5秒无人观看然后触发kBroadcastStreamNoneReader事件
|
||||
extern const std::string kStreamNoneReaderDelayMS;
|
||||
extern const std::string kNoRecordStreamNoneReaderDelayMS;
|
||||
// 等待流注册超时时间,收到播放器后请求后,如果未找到相关流,服务器会等待一定时间,
|
||||
// 如果在这个时间内,相关流注册上了,那么服务器会立即响应播放器播放成功,
|
||||
// 否则会最多等待kMaxStreamWaitTimeMS毫秒,然后响应播放器播放失败
|
||||
|
||||
@ -1,169 +1,308 @@
|
||||
/*
|
||||
* 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 "HlsMaker.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
//最小允许设置为0,0个切片代表点播
|
||||
_seg_number = seg_number;
|
||||
_seg_duration = seg_duration;
|
||||
_seg_keep = seg_keep;
|
||||
}
|
||||
|
||||
HlsMaker::~HlsMaker() {
|
||||
}
|
||||
|
||||
|
||||
void HlsMaker::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 HlsMaker::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 HlsMaker::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 HlsMaker::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 HlsMaker::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);
|
||||
}
|
||||
|
||||
bool HlsMaker::isLive() {
|
||||
return _seg_number != 0;
|
||||
}
|
||||
|
||||
bool HlsMaker::isKeep() {
|
||||
return _seg_keep;
|
||||
}
|
||||
|
||||
void HlsMaker::clear() {
|
||||
_file_index = 0;
|
||||
_last_timestamp = 0;
|
||||
_last_seg_timestamp = 0;
|
||||
_seg_dur_list.clear();
|
||||
_last_file_name.clear();
|
||||
}
|
||||
|
||||
}//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 "Util/File.h"
|
||||
#include "HlsMaker.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 {
|
||||
|
||||
HlsMaker::HlsMaker(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void HlsMaker::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 HlsMaker::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 HlsMaker::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 HlsMaker::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 HlsMaker::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 HlsMaker::isLive() {
|
||||
return _seg_number != 0;
|
||||
}
|
||||
|
||||
bool HlsMaker::isKeep() {
|
||||
return _seg_keep;
|
||||
}
|
||||
|
||||
void HlsMaker::clear() {
|
||||
_file_index = 0;
|
||||
_last_timestamp = 0;
|
||||
_last_seg_timestamp = 0;
|
||||
_seg_dur_list.clear();
|
||||
_last_file_name.clear();
|
||||
|
||||
}
|
||||
|
||||
std::string HlsMaker::getM3u8TSBody(const std::string &file_content) {
|
||||
|
||||
string new_file = file_content;
|
||||
if (file_content.find("#EXT-X-ENDLIST") != file_content.npos) {
|
||||
//找到了,则去掉"#EXT-X-ENDLIST"
|
||||
new_file = file_content.substr(0, file_content.length() - 15);
|
||||
}
|
||||
|
||||
string body = new_file.substr(new_file.find_last_of("#"));
|
||||
//此时的body为
|
||||
//#EXTINF:4.534,
|
||||
//2022-09-14/08/2022-09-14_08-35-16.ts
|
||||
string extinf = body.substr(0, body.find(",")+2);
|
||||
string tsFile = body.substr(body.find_last_of("/") + 1);
|
||||
body.append("#EXT-X-ENDLIST\n");
|
||||
|
||||
return extinf + tsFile + "#EXT-X-ENDLIST\n";
|
||||
}
|
||||
|
||||
std::string HlsMaker::getTsFile(const std::string &file_content) {
|
||||
// 最后一个TS的body为
|
||||
// 2022-09-13/13/58-13_43.ts
|
||||
// #EXT-X-ENDLIST
|
||||
string body = file_content.substr(file_content.find_last_of(",") + 2);
|
||||
string ts_file_name = body.substr(body.find_last_of("/") + 1);
|
||||
if (ts_file_name.find("#EXT-X-ENDLIST") == ts_file_name.npos ) {
|
||||
ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 4); //没找到,去掉“.ts\n”,只留名字
|
||||
} else {
|
||||
ts_file_name = ts_file_name.substr(0, ts_file_name.length() - 19); //找到的话,去掉“.ts\n#EXT-X-ENDLIST\n”,只留名字
|
||||
}
|
||||
|
||||
return ts_file_name;
|
||||
}
|
||||
|
||||
void HlsMaker::createM3u8FileForRecord() {
|
||||
// 1.读取直播目录下的m3u8文件,获取当前的ts文件以及时长,并生成m3u8文件的路径
|
||||
string live_file = File::loadFile((getPathPrefix() + "/hls.m3u8").data());
|
||||
if (live_file.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
string body = getM3u8TSBody(live_file);
|
||||
string ts_file_name = getTsFile(live_file); // ts_file: 2022-09-14_11-06-03
|
||||
string m3u8_file = getPathPrefix() + "/" + ts_file_name.substr(0, 10) + "/" + ts_file_name.substr(11, 2) + "/";
|
||||
|
||||
// 2.判断该目录下有没有m3u8文件,没有的话,生成第一个m3u8文件,有的话,重命名
|
||||
int handle = -1;
|
||||
DIR *dir_info = opendir(m3u8_file.data());
|
||||
struct dirent *dir_entry;
|
||||
if (dir_info) {
|
||||
while ((dir_entry =readdir(dir_info)) != NULL) {
|
||||
if (end_with(dir_entry->d_name, ".m3u8")) {
|
||||
handle = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir_info);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (-1 == handle) {//第一次播放流
|
||||
_m3u8_file_path = m3u8_file + ts_file_name + ".m3u8";
|
||||
_is_close_stream = false;
|
||||
} else {//断流过,一次以上播放
|
||||
if (_is_close_stream) {
|
||||
_m3u8_file_path = m3u8_file + ts_file_name + ".m3u8";
|
||||
_is_close_stream = false;
|
||||
}
|
||||
if (_m3u8_file_path.length() == 0) { //服务重启后,进来,_m3u8_file_path为空
|
||||
_m3u8_file_path = m3u8_file + ts_file_name + ".m3u8";
|
||||
}
|
||||
}
|
||||
|
||||
if (_m3u8_file_path.empty()) {
|
||||
WarnL << "create m3u8 file failed, _m3u8_file_path is empty." ;
|
||||
return;
|
||||
}
|
||||
|
||||
//3.写m3u8文件
|
||||
string m3u8Header = "#EXTM3U\n"
|
||||
"#EXT-X-PLAYLIST-TYPE:EVENT\n"
|
||||
"#EXT-X-VERSION:4\n"
|
||||
"#EXT-X-TARGETDURATION:2\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:0\n";
|
||||
|
||||
if (access(_m3u8_file_path.data(), 0) != 0) { //文件不存在
|
||||
auto file = File::create_file(_m3u8_file_path.data(), "wb");
|
||||
if (file) {
|
||||
fwrite(m3u8Header.data(), m3u8Header.size(), 1, file);
|
||||
fwrite(body.data(), body.size(), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
} else {
|
||||
// 第二次进来,去掉 "#EXT-X-ENDLIST\n",再重新追加file_content,保存文件
|
||||
auto file = File::create_file(_m3u8_file_path.data(), "r+");
|
||||
if (file) {
|
||||
fseek(file, -15, SEEK_END);
|
||||
fwrite(body.data(), body.size(), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@ -1,122 +1,140 @@
|
||||
/*
|
||||
* 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 HLSMAKER_H
|
||||
#define HLSMAKER_H
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMaker {
|
||||
public:
|
||||
/**
|
||||
* @param seg_duration 切片文件长度
|
||||
* @param seg_number 切片个数
|
||||
* @param seg_keep 是否保留切片文件
|
||||
*/
|
||||
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
virtual ~HlsMaker();
|
||||
|
||||
/**
|
||||
* 写入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();
|
||||
|
||||
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) {};
|
||||
|
||||
/**
|
||||
* 关闭上个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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSMAKER_H
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMaker {
|
||||
public:
|
||||
/**
|
||||
* @param seg_duration 切片文件长度
|
||||
* @param seg_number 切片个数
|
||||
* @param seg_keep 是否保留切片文件
|
||||
*/
|
||||
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
virtual ~HlsMaker();
|
||||
|
||||
/**
|
||||
* 写入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;
|
||||
|
||||
public:
|
||||
std::map<uint64_t /*index*/, std::string /*file_path*/> _segment_file_paths;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSMAKERSUB_H
|
||||
|
||||
@ -1,171 +1,170 @@
|
||||
/*
|
||||
* 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 "HlsMakerImp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "Util/File.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
const string ¶ms,
|
||||
uint32_t bufSize,
|
||||
float seg_duration,
|
||||
uint32_t seg_number,
|
||||
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
_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;
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
clearCache(false, true);
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache() {
|
||||
clearCache(true, false);
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||
//录制完了
|
||||
flushLastSegment(eof);
|
||||
if (!isLive()||isKeep()) {
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
|
||||
//hls直播才删除文件
|
||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||
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 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 << "_" << index << ".ts";
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if (isLive()) {
|
||||
_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();
|
||||
}
|
||||
if (_params.empty()) {
|
||||
return segment_name;
|
||||
}
|
||||
return segment_name + "?" + _params;
|
||||
}
|
||||
|
||||
void HlsMakerImp::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 HlsMakerImp::onWriteSegment(const char *data, size_t len) {
|
||||
if (_file) {
|
||||
fwrite(data, len, 1, _file.get());
|
||||
}
|
||||
if (_media_src) {
|
||||
_media_src->onSegmentSize(len);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMakerImp::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 HlsMakerImp::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> HlsMakerImp::makeFile(const string &file, bool setbuf) {
|
||||
auto file_buf = _file_buf;
|
||||
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (ret && setbuf) {
|
||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
|
||||
_info.app = app;
|
||||
_info.stream = stream_id;
|
||||
_info.vhost = vhost;
|
||||
}
|
||||
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
return _media_src;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 "HlsMakerImp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMakerImp::HlsMakerImp(
|
||||
const string &m3u8_file,
|
||||
const string ¶ms,
|
||||
uint32_t bufSize,
|
||||
float seg_duration,
|
||||
uint32_t seg_number,
|
||||
bool seg_keep):HlsMaker(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;
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
clearCache(false, true);
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache() {
|
||||
clearCache(true, false);
|
||||
}
|
||||
|
||||
void HlsMakerImp::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();
|
||||
|
||||
//删除缓存的m3u8文件
|
||||
File::delete_file((_path_prefix + "/hls.m3u8").data());
|
||||
}
|
||||
|
||||
string HlsMakerImp::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 HlsMakerImp::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 HlsMakerImp::onWriteSegment(const char *data, size_t len) {
|
||||
if (_file) {
|
||||
fwrite(data, len, 1, _file.get());
|
||||
}
|
||||
if (_media_src) {
|
||||
_media_src->onSegmentSize(len);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMakerImp::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 HlsMakerImp::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> HlsMakerImp::makeFile(const string &file, bool setbuf) {
|
||||
auto file_buf = _file_buf;
|
||||
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (ret && setbuf) {
|
||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
|
||||
_info.app = app;
|
||||
_info.stream = stream_id;
|
||||
_info.vhost = vhost;
|
||||
}
|
||||
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
return _media_src;
|
||||
}
|
||||
|
||||
std::string HlsMakerImp::getPathPrefix() {
|
||||
return _path_prefix;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
@ -1,77 +1,78 @@
|
||||
/*
|
||||
* 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 HLSMAKERIMP_H
|
||||
#define HLSMAKERIMP_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
#include "HlsMaker.h"
|
||||
#include "HlsMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMakerImp : public HlsMaker{
|
||||
public:
|
||||
HlsMakerImp(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);
|
||||
|
||||
~HlsMakerImp() 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;
|
||||
|
||||
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;
|
||||
toolkit::EventPoller::Ptr _poller;
|
||||
std::map<uint64_t/*index*/,std::string/*file_path*/> _segment_file_paths;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSMAKERIMP_H
|
||||
/*
|
||||
* 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 "HlsMaker.h"
|
||||
#include "HlsMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMakerImp : public HlsMaker{
|
||||
public:
|
||||
HlsMakerImp(
|
||||
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);
|
||||
|
||||
~HlsMakerImp() 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,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
@ -26,7 +26,6 @@ public:
|
||||
GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep);
|
||||
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
|
||||
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
|
||||
|
||||
_option = option;
|
||||
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
//清空上次的残余文件
|
||||
@ -73,6 +72,12 @@ public:
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
|
||||
}
|
||||
void startRecord(bool flag) {
|
||||
_hls->startRecord(flag);
|
||||
_is_record = flag;
|
||||
}
|
||||
|
||||
bool getRecordFlag() { return _is_record; }
|
||||
|
||||
private:
|
||||
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
|
||||
@ -88,6 +93,7 @@ private:
|
||||
bool _clear_cache = false;
|
||||
ProtocolOption _option;
|
||||
std::shared_ptr<HlsMakerImp> _hls;
|
||||
bool _is_record = false;
|
||||
};
|
||||
}//namespace mediakit
|
||||
#endif //HLSRECORDER_H
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
@ -39,7 +39,7 @@ MP4Recorder::~MP4Recorder() {
|
||||
}
|
||||
|
||||
void MP4Recorder::createFile() {
|
||||
closeFile();
|
||||
//closeFile();
|
||||
auto date = getTimeStr("%Y-%m-%d");
|
||||
auto time = getTimeStr("%H-%M-%S");
|
||||
auto full_path_tmp = _folder_path + date + "/." + time + ".mp4";
|
||||
@ -93,7 +93,7 @@ void MP4Recorder::asyncClose() {
|
||||
|
||||
void MP4Recorder::closeFile() {
|
||||
if (_muxer) {
|
||||
asyncClose();
|
||||
//asyncClose();
|
||||
_muxer = nullptr;
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,7 @@ bool MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
||||
// 2、到了切片时间,并且只有音频
|
||||
// 3、到了切片时间,有视频并且遇到视频的关键帧
|
||||
_last_dts = 0;
|
||||
createFile();
|
||||
//createFile();
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ bool MP4Recorder::addTrack(const Track::Ptr &track) {
|
||||
}
|
||||
|
||||
void MP4Recorder::resetTracks() {
|
||||
closeFile();
|
||||
//closeFile();
|
||||
_tracks.clear();
|
||||
_have_video = false;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user