From c440c45ce4603c58960acb71867093f546629e5d Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Sun, 3 May 2026 13:34:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=81=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E5=BA=A6=E6=8E=A2=E9=92=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- postman/ZLMediaKit.postman_collection.json | 51 ++++++++++++++++++++++ server/WebApi.cpp | 33 ++++++++++++++ src/Common/MultiMediaSourceMuxer.cpp | 29 ++++++++++++ src/Common/MultiMediaSourceMuxer.h | 16 +++++++ 4 files changed, 129 insertions(+) diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index c7d1b26b..30942c93 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -3279,6 +3279,57 @@ } }, "response": [] + }, + { + "name": "添加探针(addProbe)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/addProbe?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=test&probe_ms=5000", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "addProbe" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置)", + "type": "text" + }, + { + "key": "vhost", + "value": "{{defaultVhost}}", + "description": "流的虚拟主机,例如__defaultVhost__", + "type": "text" + }, + { + "key": "app", + "value": "live", + "description": "流的应用名,例如live", + "type": "text" + }, + { + "key": "stream", + "value": "test", + "description": "流的id名,例如test", + "type": "text" + }, + { + "key": "probe_ms", + "value": "5000", + "description": "探针时长,单位毫秒", + "type": "text" + } + ] + } + }, + "response": [] } ], "event": [ diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 97363caa..6e51f666 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -2651,6 +2651,39 @@ void installWebApi() { invoker(200, headerOut, val.toStyledString()); }); }); + + api_regist("/index/api/addProbe", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("vhost", "app", "stream", "probe_ms"); + + std::string vhost = allArgs["vhost"]; + std::string app = allArgs["app"]; + std::string stream = allArgs["stream"]; + uint32_t probe_ms = allArgs["probe_ms"]; + + auto src = MediaSource::find(vhost, app, stream); + if (!src) { + throw ApiRetException("can not find the stream", API::NotFound); + } + src->getOwnerPoller()->async([=]() mutable { + src->getMuxer()->addProbe(probe_ms, [=](const std::list &info_list) mutable { + for (const auto &info : info_list) { + Json::Value item; + item["codec"] = getCodecName(info.codec_id); + item["track_type"] = getTrackString(getTrackType(info.codec_id)); + item["dts"] = (Json::Int64)info.dts; + item["pts"] = (Json::Int64)info.pts; + item["recv_stamp"] = (Json::Int64)info.recv_stamp; + item["frame_size"] = (Json::UInt)info.frame_size; + item["index"] = info.index; + item["key_frame"] = info.key_frame; + item["config_frame"] = info.config_frame; + val["data"].append(std::move(item)); + } + invoker(200, headerOut, val.toStyledString()); + }); + }); + }); } void unInstallWebApi(){ diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 4c1f10eb..43645abc 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -818,7 +818,36 @@ void MultiMediaSourceMuxer::resetTracks() { } } +void MultiMediaSourceMuxer::addProbe(uint32_t probe_ms, const std::function &info_list)> &cb) { + CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread()); + auto info_list = std::make_shared>(); + Ticker ticker; + _on_frame = [info_list, ticker](const Frame::Ptr &frame) mutable { + FrameInfo info; + info.codec_id = frame->getCodecId(); + info.dts = frame->dts(); + info.pts = frame->pts(); + info.recv_stamp = ticker.createdTime(); + info.frame_size = frame->size(); + info.index = frame->getIndex(); + info.key_frame = frame->keyFrame(); + info.config_frame = frame->configFrame(); + info_list->emplace_back(info); + }; + std::weak_ptr weak_self = shared_from_this(); + getOwnerPoller(MediaSource::NullMediaSource())->doDelayTask(probe_ms, [weak_self, cb, info_list]() { + if (auto strong_self = weak_self.lock()) { + strong_self->_on_frame = nullptr; + } + cb(*info_list); + return 0; + }); +} + bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { + if (_on_frame) { + _on_frame(frame_in); + } auto frame = frame_in; if (_option.modify_stamp != ProtocolOption::kModifyStampOff) { // 时间戳不采用原始的绝对时间戳 [AUTO-TRANSLATED:8beb3bf7] diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index d2cf23b0..7b91641a 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -25,6 +25,18 @@ namespace mediakit { +struct FrameInfo { + CodecId codec_id = CodecInvalid; + int64_t dts = 0; + int64_t pts = 0; + int64_t recv_stamp = 0; + size_t frame_size = 0; + + int index = 0; + bool key_frame = false; + bool config_frame = false; +}; + class MultiMediaSourceMuxer : public MediaSourceEventInterceptor, public MediaSink, public toolkit::noncopyable, public std::enable_shared_from_this{ public: using Ptr = std::shared_ptr; @@ -188,6 +200,9 @@ public: #if defined(ENABLE_RTPPROXY) void forEachRtpSender(const std::function &cb) const; #endif // ENABLE_RTPPROXY + + void addProbe(uint32_t probe_ms, const std::function &info_list)> &cb); + protected: /////////////////////////////////MediaSink override///////////////////////////////// @@ -231,6 +246,7 @@ private: bool _create_in_poller = false; bool _video_key_pos = false; float _dur_sec; + std::function _on_frame; std::shared_ptr _paced_sender; MediaTuple _tuple; ProtocolOption _option;