mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 10:57:50 +08:00
新增startRecordTask接口,支持录制事件视频
用户可以录制当前时间前后一定时间内的视频,配置文件gop_cache设置较大时历史视频才能录全
This commit is contained in:
parent
b1e1a0f174
commit
c9a0025620
@ -1380,6 +1380,62 @@
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "开始事件视频录制(startRecordTask)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/startRecordTask?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=test&path=1.mp4&back_ms=10000&forward_ms=10000",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"startRecordTask"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "应用名,例如 live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "test",
|
||||
"description": "流id,例如 obs"
|
||||
},
|
||||
{
|
||||
"key": "path",
|
||||
"value": "1.mp4",
|
||||
"description": "录像文件保存相对路径,包括名称"
|
||||
},
|
||||
{
|
||||
"key": "back_ms",
|
||||
"value": "10000",
|
||||
"description": "回溯录制时长"
|
||||
},
|
||||
{
|
||||
"key": "forward_ms",
|
||||
"value": "10000",
|
||||
"description": "后续录制时长"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "设置录像速度(setRecordSpeed)",
|
||||
"request": {
|
||||
|
||||
@ -1776,6 +1776,30 @@ void installWebApi() {
|
||||
});
|
||||
});
|
||||
|
||||
api_regist("/index/api/startRecordTask",[](API_ARGS_MAP_ASYNC){
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream", "path", "back_ms", "forward_ms");
|
||||
|
||||
auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
||||
if (!src) {
|
||||
throw ApiRetException("can not find the stream", API::NotFound);
|
||||
}
|
||||
|
||||
src->getOwnerPoller()->async([=]() mutable {
|
||||
std::string err;
|
||||
std::string path;
|
||||
try {
|
||||
path = src->getMuxer()->startRecord(allArgs["path"], allArgs["back_ms"], allArgs["forward_ms"]);
|
||||
} catch (std::exception &ex) {
|
||||
err = ex.what();
|
||||
}
|
||||
val["code"] = err.empty() ? API::Success : API::OtherFailed;
|
||||
val["data"]["path"] = path;
|
||||
val["msg"] = err;
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
// 设置录像流播放速度 [AUTO-TRANSLATED:a8d82298]
|
||||
// Set the playback speed of the recording stream
|
||||
api_regist("/index/api/setRecordSpeed", [](API_ARGS_MAP_ASYNC) {
|
||||
@ -1888,11 +1912,13 @@ void installWebApi() {
|
||||
// http://127.0.0.1/index/api/deleteRecordDirectroy?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01-01
|
||||
api_regist("/index/api/deleteRecordDirectory", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream", "period");
|
||||
CHECK_ARGS("vhost", "app", "stream");
|
||||
auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"], ""};
|
||||
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]);
|
||||
auto period = allArgs["period"];
|
||||
if (!period.empty()) {
|
||||
record_path = record_path + period + "/";
|
||||
}
|
||||
|
||||
bool recording = false;
|
||||
auto name = allArgs["name"];
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include <math.h>
|
||||
#include "Common/config.h"
|
||||
#include "MultiMediaSourceMuxer.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@ -400,6 +401,88 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||
}
|
||||
}
|
||||
|
||||
std::string MultiMediaSourceMuxer::startRecord(const std::string &file_path, uint32_t back_time_ms, uint32_t forward_time_ms) {
|
||||
#if !defined(ENABLE_MP4)
|
||||
throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试");
|
||||
#else
|
||||
if (!_ring) {
|
||||
throw std::runtime_error("frame gop cache disabled, start record event video failed");
|
||||
}
|
||||
auto path = Recorder::getRecordPath(Recorder::type_mp4, _tuple, _option.mp4_save_path);
|
||||
path += file_path;
|
||||
TraceL << "mp4 save path: " << path;
|
||||
|
||||
auto muxer = std::make_shared<MP4Muxer>();
|
||||
muxer->openMP4(path);
|
||||
for (auto &track : MediaSink::getTracks()) {
|
||||
muxer->addTrack(track);
|
||||
}
|
||||
muxer->addTrackCompleted();
|
||||
|
||||
std::list<Frame::Ptr> history;
|
||||
_ring->flushGop([&](const Frame::Ptr &frame) { history.emplace_back(frame); });
|
||||
if (!history.empty()) {
|
||||
auto now_dts = history.back()->dts();
|
||||
|
||||
decltype(history)::iterator pos = history.end();
|
||||
for (auto it = history.rbegin(); it != history.rend(); ++it) {
|
||||
auto &frame = *it;
|
||||
if (frame->getTrackType() != TrackVideo || (!frame->configFrame() && !frame->keyFrame())) {
|
||||
continue;
|
||||
}
|
||||
// 如果视频关键帧到末尾的时长超过一定的时间,那前面的数据应该全部删除
|
||||
if (frame->dts() + back_time_ms < now_dts) {
|
||||
pos = it.base();
|
||||
--pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos != history.end()) {
|
||||
// 移除前面过多的数据
|
||||
TraceL << "clear history video: " << history.front()->dts() << " -> " << (*pos)->dts();
|
||||
history.erase(history.begin(), pos);
|
||||
}
|
||||
if (!history.empty()) {
|
||||
auto &front = history.front();
|
||||
InfoL << "start record: " << path
|
||||
<< ", start_dts: " << front->dts() << ", key_frame: " << front->keyFrame() << ", config_frame: " << front->configFrame()
|
||||
<< ", now_dts: " << now_dts;
|
||||
}
|
||||
|
||||
for (auto &frame : history) {
|
||||
muxer->inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
auto reader = _ring->attach(MultiMediaSourceMuxer::getOwnerPoller(MediaSource::NullMediaSource()), false);
|
||||
uint64_t now_dts = 0;
|
||||
int selected_index = -1;
|
||||
reader->setReadCB([muxer, now_dts, selected_index, forward_time_ms, reader, path](const Frame::Ptr &frame) mutable {
|
||||
// 循环引用自身
|
||||
if (!now_dts) {
|
||||
now_dts = frame->dts();
|
||||
selected_index = frame->getIndex();
|
||||
}
|
||||
if (frame->getIndex() == selected_index && now_dts + forward_time_ms < frame->dts()) {
|
||||
InfoL << "stop record: " << path << ", end dts: " << frame->dts();
|
||||
WorkThreadPool::Instance().getPoller()->async([muxer]() { muxer->closeMP4(); });
|
||||
reader = nullptr;
|
||||
return;
|
||||
}
|
||||
muxer->inputFrame(frame);
|
||||
});
|
||||
std::weak_ptr<RingType::RingReader> weak_reader = reader;
|
||||
reader->setDetachCB([weak_reader]() {
|
||||
if (auto strong_reader = weak_reader.lock()) {
|
||||
// 防止循环引用
|
||||
strong_reader->setReadCB(nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
return path;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 此函数可能跨线程调用 [AUTO-TRANSLATED:e8c5f74d]
|
||||
// This function may be called across threads
|
||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
|
||||
@ -122,6 +122,15 @@ public:
|
||||
*/
|
||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const std::string &custom_path, size_t max_second) override;
|
||||
|
||||
/**
|
||||
* 开始录制mp4
|
||||
* @param file_path mp4相对路径
|
||||
* @param back_time_ms 回溯录制时长
|
||||
* @param forward_time_ms 后续录制时长
|
||||
* @return 录制文件绝对路径
|
||||
*/
|
||||
std::string startRecord(const std::string &file_path, uint32_t back_time_ms, uint32_t forward_time_ms);
|
||||
|
||||
/**
|
||||
* 获取录制状态
|
||||
* @param type 录制类型
|
||||
|
||||
Loading…
Reference in New Issue
Block a user