增加hls的http服务
Some checks are pending
CI / host-build (push) Waiting to run
CI / rk3588-cross-build (push) Waiting to run

This commit is contained in:
sladro 2026-01-06 10:01:29 +08:00
parent d7c944d898
commit 0cc31ca9b9
2 changed files with 273 additions and 0 deletions

View File

@ -305,6 +305,29 @@ set_target_properties(storage PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
)
# zlm_http plugin (embedded HTTP file server via ZLMediaKit)
if(RK3588_ENABLE_ZLMEDIAKIT AND RK_ZLMK_API_LIB)
add_library(zlm_http SHARED zlm_http/zlm_http_node.cpp)
target_include_directories(zlm_http PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/third_party)
target_compile_definitions(zlm_http PRIVATE RK3588_ENABLE_ZLMEDIAKIT)
target_include_directories(zlm_http PRIVATE ${RK_ZLMEDIAKIT_INCLUDE_DIR})
target_link_libraries(zlm_http PRIVATE project_options Threads::Threads ${RK_ZLMK_API_LIB})
set_target_properties(zlm_http PROPERTIES
OUTPUT_NAME "zlm_http"
LIBRARY_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN"
)
add_custom_command(TARGET zlm_http POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RK_ZLMK_API_LIB}" $<TARGET_FILE_DIR:zlm_http>
)
install(TARGETS zlm_http
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
)
endif()
install(TARGETS input_rtsp input_file publish preprocess ai_yolo osd alarm storage ai_scheduler
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins

View File

@ -0,0 +1,250 @@
#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <string>
#include "node.h"
#if defined(RK3588_ENABLE_ZLMEDIAKIT)
extern "C" {
#include "mk_events.h"
#include "mk_mediakit.h"
}
#endif
namespace rk3588 {
#if defined(RK3588_ENABLE_ZLMEDIAKIT)
namespace {
struct HttpServeState {
std::mutex mu;
std::string root;
std::string prefix;
uint16_t port = 0;
bool started = false;
};
HttpServeState& GState() {
static HttpServeState st;
return st;
}
static inline bool StartsWith(const std::string& s, const std::string& prefix) {
return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
}
static inline std::string TrimLeadingSlash(std::string s) {
while (!s.empty() && s.front() == '/') s.erase(s.begin());
return s;
}
static inline bool HasPathTraversal(const std::string& rel) {
// Minimal hardening: reject obvious traversal and Windows drive/UNC paths.
if (rel.find("..") != std::string::npos) return true;
if (rel.find('\\') != std::string::npos) return true;
if (rel.find(':') != std::string::npos) return true;
return false;
}
static inline const char* ContentTypeForPath(const std::filesystem::path& p) {
auto ext = p.extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
if (ext == ".m3u8") return "application/vnd.apple.mpegurl";
if (ext == ".ts") return "video/mp2t";
if (ext == ".mp4") return "video/mp4";
if (ext == ".m4s") return "video/iso.segment";
if (ext == ".mpd") return "application/dash+xml";
if (ext == ".vtt") return "text/vtt";
return "application/octet-stream";
}
void API_CALL OnHttpRequest(const mk_parser parser,
const mk_http_response_invoker invoker,
int* consumed,
const mk_sock_info /*sender*/) {
if (!parser || !invoker || !consumed) return;
const char* method_c = mk_parser_get_method(parser);
const char* url_c = mk_parser_get_url(parser);
if (!method_c || !url_c) return;
const std::string method(method_c);
const std::string url(url_c);
std::string root;
std::string prefix;
{
auto& st = GState();
std::lock_guard<std::mutex> lock(st.mu);
root = st.root;
prefix = st.prefix;
}
if (root.empty()) return;
std::string rel_url = url;
if (!prefix.empty() && prefix != "/") {
if (!StartsWith(rel_url, prefix)) return;
rel_url.erase(0, prefix.size());
if (rel_url.empty()) rel_url = "/";
}
rel_url = TrimLeadingSlash(rel_url);
if (rel_url.empty()) return;
if (HasPathTraversal(rel_url)) {
*consumed = 1;
const char* hdr[] = {
"Access-Control-Allow-Origin", "*",
"Cache-Control", "no-cache",
nullptr
};
mk_http_response_invoker_do_string(invoker, 403, hdr, "Forbidden");
return;
}
std::filesystem::path full = std::filesystem::path(root) / std::filesystem::path(rel_url);
std::error_code ec;
if (std::filesystem::is_directory(full, ec)) {
full /= "index.m3u8";
}
if (ec || !std::filesystem::exists(full, ec) || ec || !std::filesystem::is_regular_file(full, ec) || ec) {
return; // fallthrough to ZLMediaKit default handlers
}
const char* ct = ContentTypeForPath(full);
if (method == "OPTIONS") {
*consumed = 1;
const char* hdr[] = {
"Access-Control-Allow-Origin", "*",
"Access-Control-Allow-Methods", "GET, OPTIONS",
"Access-Control-Allow-Headers", "Range, Origin, Accept, Content-Type",
"Access-Control-Max-Age", "86400",
"Cache-Control", "no-cache",
nullptr
};
mk_http_response_invoker_do_string(invoker, 204, hdr, "");
return;
}
if (method != "GET") return;
*consumed = 1;
const char* hdr[] = {
"Content-Type", ct,
"Access-Control-Allow-Origin", "*",
"Cache-Control", "no-cache",
nullptr
};
const std::string file_path = full.string();
mk_http_response_invoker_do_file(invoker, parser, hdr, file_path.c_str());
}
void EnsureZlmEnvOnce() {
static std::once_flag once;
std::call_once(once, [] {
mk_config cfg;
std::memset(&cfg, 0, sizeof(cfg));
cfg.thread_num = 0;
cfg.log_level = 2;
cfg.log_mask = LOG_CONSOLE;
mk_env_init(&cfg);
});
}
void EnsureHttpEventsOnce() {
static std::once_flag once;
std::call_once(once, [] {
mk_events ev;
std::memset(&ev, 0, sizeof(ev));
ev.on_mk_http_request = &OnHttpRequest;
mk_events_listen(&ev);
});
}
uint16_t StartHttpServerOnce(uint16_t port, bool ssl) {
auto& st = GState();
std::lock_guard<std::mutex> lock(st.mu);
if (st.started) return st.port;
auto actual = mk_http_server_start(port, ssl ? 1 : 0);
if (actual == 0) return 0;
st.started = true;
st.port = actual;
return actual;
}
} // namespace
#endif
class ZlmHttpNode final : public INode {
public:
std::string Id() const override { return id_; }
std::string Type() const override { return "zlm_http"; }
bool Init(const SimpleJson& config, const NodeContext& /*ctx*/) override {
id_ = config.ValueOr<std::string>("id", "zlm_http");
#if !defined(RK3588_ENABLE_ZLMEDIAKIT)
(void)config;
std::cerr << "[zlm_http] RK3588_ENABLE_ZLMEDIAKIT is OFF at build time\n";
return false;
#else
root_ = config.ValueOr<std::string>("root", "");
if (root_.empty()) root_ = config.ValueOr<std::string>("path", "");
prefix_ = config.ValueOr<std::string>("prefix", "/");
port_ = static_cast<uint16_t>(std::max(0, config.ValueOr<int>("port", 8080)));
ssl_ = config.ValueOr<bool>("ssl", false);
if (root_.empty()) {
std::cerr << "[zlm_http] missing config: root (or path)\n";
return false;
}
if (prefix_.empty()) prefix_ = "/";
if (prefix_.front() != '/') prefix_ = "/" + prefix_;
return true;
#endif
}
bool Start() override {
#if !defined(RK3588_ENABLE_ZLMEDIAKIT)
return false;
#else
EnsureZlmEnvOnce();
EnsureHttpEventsOnce();
{
auto& st = GState();
std::lock_guard<std::mutex> lock(st.mu);
st.root = root_;
st.prefix = prefix_;
}
auto actual = StartHttpServerOnce(port_, ssl_);
if (actual == 0) {
std::cerr << "[zlm_http] failed to start HTTP server on port " << port_ << "\n";
return false;
}
std::cout << "[zlm_http] http file server started, port=" << actual
<< " root=" << root_ << " prefix=" << prefix_ << "\n";
return true;
#endif
}
void Stop() override {
// ZLMediaKit mk_api doesn't expose per-server stop; keep it running for process lifetime.
}
private:
std::string id_;
std::string root_;
std::string prefix_ = "/";
uint16_t port_ = 8080;
bool ssl_ = false;
};
REGISTER_NODE(ZlmHttpNode, "zlm_http");
} // namespace rk3588