From 7a40c9a7f312a8b5375531b408626d9eb8967ef7 Mon Sep 17 00:00:00 2001 From: GuanYuankai Date: Wed, 22 Oct 2025 01:50:12 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=A1=E4=BB=A4=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=EF=BC=8C=E8=BD=AC=E5=8F=91HLS=E5=92=8CWebRTC=E3=80=82?= =?UTF-8?q?=E5=90=8E=E6=9C=9F=E5=8F=AF=E4=BB=A5=E7=9B=B4=E6=8E=A5=E9=9B=86?= =?UTF-8?q?=E6=88=90SRT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/c_cpp_properties.json | 1 - docker-compose.yml | 8 +++ docker/Dockerfile | 1 + mediamtx.yml | 23 ++++++ src/main.cpp | 6 +- src/streamer/main.cpp | 130 ++++++++++++++-------------------- src/web/web_server.cc | 70 +++++++++--------- 7 files changed, 123 insertions(+), 116 deletions(-) create mode 100644 mediamtx.yml diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index dccac7e..c1fe5f3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -8,7 +8,6 @@ "/usr/include/gstreamer-1.0", "/usr/include/glib-2.0", "/usr/lib/aarch64-linux-gnu/glib-2.0/include" - // (GStreamer 依赖 GLib, GLib的这个 .include 路径是必须的) ], "defines": [ "CROW_USE_BOOST" diff --git a/docker-compose.yml b/docker-compose.yml index 7d5382c..33d10d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,14 @@ services: - 114.114.114.114 command: sleep infinity + media-gateway: + image: bluenviron/mediamtx:latest + container_name: media-gateway + network_mode: "host" + privileged: true + volumes: + - ./mediamtx.yml:/mediamtx.yml + mqtt-broker: image: eclipse-mosquitto:2.0 container_name: mqtt-broker diff --git a/docker/Dockerfile b/docker/Dockerfile index a4662f4..651dc7a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,6 +45,7 @@ RUN apt-get update && \ gstreamer1.0-x \ gstreamer1.0-alsa \ gstreamer1.0-pulseaudio \ + gstreamer1.0-rtsp \ libopencv-dev \ nmap \ && \ diff --git a/mediamtx.yml b/mediamtx.yml new file mode 100644 index 0000000..4e2dcba --- /dev/null +++ b/mediamtx.yml @@ -0,0 +1,23 @@ +# mediamtx.yml (古早版本兼容模式) +logLevel: debug + +# [修复] 使用 'Address' 语法 (解决了 'unknown field "hlsPort"') +rtsp: true +rtspAddress: :8554 + +hls: true +hlsAddress: :8888 +hlsVariant: lowLatency + +webRTC: true +webRTCAddress: :8889 + +# 路径配置 +paths: + # 'all_others' 解决了 'aliases' 错误 + all_others: + + # [修复] 使用 'publishIPs' (解决了 'unknown field "publishers"') + publishIPs: [127.0.0.1] + + # [修复] 移除所有低延迟HLS配置 (解决了 'unknown field "hlsVariant"') \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6154af2..6d5801a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,7 +53,7 @@ int main(int argc, char* argv[]) { std::cerr << "Log initialization failed: " << ex.what() << std::endl; return 1; } - // --- 数据库初始化代码 --- + spdlog::info("Initializing Data Storage..."); if (!DataStorage::getInstance().initialize("edge_proxy_data.db")) { spdlog::critical("Failed to initialize DataStorage. Exiting."); @@ -71,7 +71,7 @@ int main(int argc, char* argv[]) { } else { spdlog::error("Failed to store PROCESSED data for device '{}'", data.device_id); } - // + live_data_cache.update_data(data.device_id, data.data_json); if (mqtt_client.is_connected()) { // 网络正常,直接上报 @@ -88,7 +88,7 @@ int main(int argc, char* argv[]) { DeviceManager device_manager(g_io_context, report_to_mqtt); MqttRouter mqtt_router(mqtt_client, device_manager); - std::vector listen_ports = { 8888 }; + std::vector listen_ports = { 12345 }; TCPServer tcp_server(g_io_context, listen_ports, mqtt_client); SystemMonitor::SystemMonitor monitor; diff --git a/src/streamer/main.cpp b/src/streamer/main.cpp index 0054759..5efcbe3 100644 --- a/src/streamer/main.cpp +++ b/src/streamer/main.cpp @@ -1,87 +1,69 @@ /* * 文件: src/streamer/main.cpp - * 目标: 阶段 1.0 - HLS 网页播放 - * 职责: 1. 运行采集管线 (rtspsrc -> ... -> appsink) - * 2. 运行 HLS 编码管线 (appsrc -> ... -> hlssink) - * 3. 在 C++ 回调中,将 (1) 的样本推送给 (2) + * 目标: 阶段 2.2 - H.265 直通 (适配 Rockchip PPA) + * 职责: 1. 运行采集管线 (rtspsrc -> h265parse -> appsink) + * 2. 运行 RTSP 推流管线 (appsrc -> h265parse -> rtspclientsink) [无 pay 插件] + * 3. 在 C++ 回调中,将 (1) 的 H.265 样本推送给 (2) */ #include #include #include #include -#include +#include typedef struct { GMainLoop *loop; - GstElement *ingest_pipeline; // 采集管线 - GstElement *hls_pipeline; // HLS 编码管线 - GstElement *hls_appsrc; // HLS 管线的 "appsrc" 元件 + GstElement *ingest_pipeline; + GstElement *rtsp_pipeline; + GstElement *rtsp_appsrc; } AppData; /** - * @brief 这是核心回调函数 (Callback) - * 当 'appsink' 成功拉出一个解码后的帧时,此函数被调用 + * @brief 核心回调函数 (Callback) */ static GstFlowReturn on_new_sample (GstAppSink * appsink, gpointer user_data) { AppData *data = (AppData *) user_data; static guint frame_count = 0; - // 从 appsink 拉取样本 (包含解码后的原始帧) + GstSample *sample = gst_app_sink_pull_sample (appsink); if (sample == NULL) { return GST_FLOW_ERROR; } - - // 检查 HLS 编码管线的 appsrc 是否已准备好 - if (data->hls_appsrc) { - // 将样本(sample)直接推送到 hls_appsrc - // GStreamer 会自动处理缓冲和线程 - GstFlowReturn ret = gst_app_src_push_sample (GST_APP_SRC (data->hls_appsrc), sample); + if (data->rtsp_appsrc) { + GstFlowReturn ret = gst_app_src_push_sample (GST_APP_SRC (data->rtsp_appsrc), sample); if (ret != GST_FLOW_OK) { - g_warning ("Failed to push sample to HLS appsrc"); + g_warning ("Failed to push sample to RTSP appsrc (ret: %d)", ret); } } - // 打印日志 frame_count++; if (frame_count % 100 == 0) { - g_print ("Hardware decoder acquired frame %u\n", frame_count); + g_print ("H.265 passthrough frame %u\n", frame_count); } - // 【重要】释放样本 - // 无论推送成功与否,我们都必须释放 sample,hls_appsrc 已持有它 - gst_sample_unref (sample); - + gst_sample_unref (sample); return GST_FLOW_OK; } int main (int argc, char *argv[]) { - AppData data = {0}; // 初始化数据结构 + AppData data = {0}; GstElement *appsink; GError *error = NULL; - gst_init (&argc, &argv); - data.loop = g_main_loop_new (NULL, FALSE); - - // --- 2. 【新增】HLS 路径和目录准备 --- - const char *hls_output_dir = "/app/hls_streams/live"; - const char *playlist_location = "/app/hls_streams/live/playlist.m3u8"; - - g_print("Creating HLS directory: %s\n", hls_output_dir); - g_autofree gchar *mkdir_cmd = g_strdup_printf("mkdir -p %s", hls_output_dir); - system(mkdir_cmd); + gst_init (&argc, &argv); + data.loop = g_main_loop_new (NULL, FALSE); const gchar *ingest_pipeline_str = - "rtspsrc location=rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301 latency=300 protocols=tcp ! " - "application/x-rtp, media=(string)video ! " - "rtpjitterbuffer latency=100 ! " - "rtph265depay ! h265parse ! queue ! mppvideodec ! " - "video/x-raw,format=NV12 ! " - "appsink name=sink emit-signals=true max-buffers=5 drop=true"; + "rtspsrc location=rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301 latency=300 protocols=tcp ! " + "application/x-rtp, media=(string)video ! " + "rtpjitterbuffer latency=100 ! " + "rtph265depay ! h265parse ! " + "appsink name=sink emit-signals=true max-buffers=5 drop=true"; - g_print ("Using Ingest pipeline:\n%s\n", ingest_pipeline_str); + g_print ("Using Ingest pipeline (H.265 Passthrough):\n%s\n", ingest_pipeline_str); data.ingest_pipeline = gst_parse_launch (ingest_pipeline_str, &error); if (error) { g_printerr ("Failed to parse ingest pipeline: %s\n", error->message); @@ -94,61 +76,55 @@ int main (int argc, char *argv[]) g_object_unref (appsink); - // --- 配置 HLS 编码管线 (HLS Pipeline) --- - g_autofree gchar *hls_pipeline_str = g_strdup_printf( - "appsrc name=source ! " // (您的) appsrc,C++代码已为其设置了 1920x1080 NV12 Caps - "mpph264enc ! " // (您的) H.264 硬件编码器 - "h264parse ! " // (您的) H.264 解析器 - "mpegtsmux ! " // [关键修复] 将 H.264 裸流打包成 MPEG-TS 流 - "hlssink " - " playlist_root=http://192.168.0.108:8080/streams/live " - " playlist_location=%s " - " location=%s/segment%%05d.ts " - " target-duration=1 " - " max-files=3", - playlist_location, - hls_output_dir + g_autofree gchar *rtsp_pipeline_str = g_strdup_printf( + "appsrc name=source ! " + "h265parse ! " + "rtspclientsink location=rtsp://127.0.0.1:8554/live latency=300" ); - g_print ("Using HLS pipeline:\n%s\n", hls_pipeline_str); - data.hls_pipeline = gst_parse_launch (hls_pipeline_str, &error); + g_print ("Using RTSP Push pipeline (H.265 Passthrough, No Payer):\n%s\n", rtsp_pipeline_str); + + data.rtsp_pipeline = gst_parse_launch (rtsp_pipeline_str, &error); if (error) { - g_printerr ("Failed to parse HLS pipeline: %s\n", error->message); + g_printerr ("Failed to parse RTSP push pipeline: %s\n", error->message); g_error_free (error); return -1; } - data.hls_appsrc = gst_bin_get_by_name (GST_BIN (data.hls_pipeline), "source"); - if (!data.hls_appsrc) { - g_printerr("Failed to get HLS appsrc element.\n"); + data.rtsp_appsrc = gst_bin_get_by_name (GST_BIN (data.rtsp_pipeline), "source"); + if (!data.rtsp_appsrc) { + g_printerr("Failed to get RTSP appsrc element.\n"); return -1; } - GstCaps *caps = gst_caps_new_simple ("video/x-raw", - "format", G_TYPE_STRING, "NV12", - "width", G_TYPE_INT, 1920, - "height", G_TYPE_INT, 1080, - "framerate", GST_TYPE_FRACTION, 25, 1, + GstCaps *caps = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", NULL); - g_object_set (data.hls_appsrc, "caps", caps, NULL); - g_object_set (data.hls_appsrc, "format", GST_FORMAT_TIME, NULL); + + g_object_set (data.rtsp_appsrc, "caps", caps, NULL); + g_object_set (data.rtsp_appsrc, "format", GST_FORMAT_TIME, NULL); + g_object_set (data.rtsp_appsrc, "is-live", TRUE, NULL); + g_object_set (data.rtsp_appsrc, "do-timestamp", TRUE, NULL); + g_object_set (data.rtsp_appsrc, "block", TRUE, NULL); + g_object_set (data.rtsp_appsrc, "max-bytes", (guint64)20000000, NULL); gst_caps_unref (caps); + // --- 启动所有管线 --- g_print ("Starting Ingest pipeline...\n"); gst_element_set_state (data.ingest_pipeline, GST_STATE_PLAYING); - - g_print ("Starting HLS pipeline...\n"); - gst_element_set_state (data.hls_pipeline, GST_STATE_PLAYING); - - g_print ("All pipelines started. Waiting for frames...\n"); + g_print ("Starting RTSP push pipeline...\n"); + gst_element_set_state (data.rtsp_pipeline, GST_STATE_PLAYING); + g_print ("All pipelines started. Waiting for H.265 frames...\n"); g_main_loop_run (data.loop); + // --- 清理 --- g_print ("Exiting...\n"); gst_element_set_state (data.ingest_pipeline, GST_STATE_NULL); - gst_element_set_state (data.hls_pipeline, GST_STATE_NULL); + gst_element_set_state (data.rtsp_pipeline, GST_STATE_NULL); gst_object_unref (data.ingest_pipeline); - gst_object_unref (data.hls_pipeline); - if (data.hls_appsrc) g_object_unref (data.hls_appsrc); + gst_object_unref (data.rtsp_pipeline); + if (data.rtsp_appsrc) g_object_unref (data.rtsp_appsrc); g_main_loop_unref (data.loop); return 0; diff --git a/src/web/web_server.cc b/src/web/web_server.cc index d6c8320..5256faa 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -111,44 +111,44 @@ void WebServer::setup_routes() { return response; }); - CROW_ROUTE((*this), "/streams//") - .methods("GET"_method) - ([this](const crow::request& req, crow::response& res, std::string stream_id, std::string filename) { + // CROW_ROUTE((*this), "/streams//") + // .methods("GET"_method) + // ([this](const crow::request& req, crow::response& res, std::string stream_id, std::string filename) { - // **安全检查**: 防止路径遍历攻击 - if (filename.find("..") != std::string::npos || stream_id.find("..") != std::string::npos) { - res.code = 400; - res.end("Bad Request"); - return; - } + // // **安全检查**: 防止路径遍历攻击 + // if (filename.find("..") != std::string::npos || stream_id.find("..") != std::string::npos) { + // res.code = 400; + // res.end("Bad Request"); + // return; + // } - // HLS 文件在磁盘上的真实路径 - std::string file_path = "/app/hls_streams/" + stream_id + "/" + filename; + // // HLS 文件在磁盘上的真实路径 + // std::string file_path = "/app/hls_streams/" + stream_id + "/" + filename; - std::ifstream file(file_path, std::ios::binary); - if (!file.is_open()) { - // 文件未找到 - res.code = 404; - res.end("Not Found"); - return; - } - // 将文件内容读入字符串流 - std::ostringstream contents; - contents << file.rdbuf(); - file.close(); - res.body = contents.str(); + // std::ifstream file(file_path, std::ios::binary); + // if (!file.is_open()) { + // // 文件未找到 + // res.code = 404; + // res.end("Not Found"); + // return; + // } + // // 将文件内容读入字符串流 + // std::ostringstream contents; + // contents << file.rdbuf(); + // file.close(); + // res.body = contents.str(); - if (filename.find(".m3u8") != std::string::npos) { - res.set_header("Content-Type", "application/vnd.apple.mpegurl"); - res.set_header("Cache-Control", "no-cache, no-store, must-revalidate"); - res.set_header("Pragma", "no-cache"); - res.set_header("Expires", "0"); - } else if (filename.find(".ts") != std::string::npos) { - res.set_header("Content-Type", "video/MP2T"); - } else { - res.set_header("Content-Type", "application/octet-stream"); - } - res.end(); - }); + // if (filename.find(".m3u8") != std::string::npos) { + // res.set_header("Content-Type", "application/vnd.apple.mpegurl"); + // res.set_header("Cache-Control", "no-cache, no-store, must-revalidate"); + // res.set_header("Pragma", "no-cache"); + // res.set_header("Expires", "0"); + // } else if (filename.find(".ts") != std::string::npos) { + // res.set_header("Content-Type", "video/MP2T"); + // } else { + // res.set_header("Content-Type", "application/octet-stream"); + // } + // res.end(); + // }); } \ No newline at end of file