添加信令服务,转发HLS和WebRTC。后期可以直接集成SRT

This commit is contained in:
GuanYuankai 2025-10-22 01:50:12 +00:00
parent 49879e1c59
commit 7a40c9a7f3
7 changed files with 123 additions and 116 deletions

View File

@ -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"

View File

@ -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

View File

@ -45,6 +45,7 @@ RUN apt-get update && \
gstreamer1.0-x \
gstreamer1.0-alsa \
gstreamer1.0-pulseaudio \
gstreamer1.0-rtsp \
libopencv-dev \
nmap \
&& \

23
mediamtx.yml Normal file
View File

@ -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"')

View File

@ -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<uint16_t> listen_ports = { 8888 };
std::vector<uint16_t> listen_ports = { 12345 };
TCPServer tcp_server(g_io_context, listen_ports, mqtt_client);
SystemMonitor::SystemMonitor monitor;

View File

@ -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 <gst/gst.h>
#include <gst/app/gstappsink.h>
#include <gst/app/gstappsrc.h>
#include <glib.h>
#include <stdlib.h>
#include <stdlib.h>
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);
}
// 【重要】释放样本
// 无论推送成功与否,我们都必须释放 samplehls_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 ! " // (您的) appsrcC++代码已为其设置了 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;

View File

@ -111,44 +111,44 @@ void WebServer::setup_routes() {
return response;
});
CROW_ROUTE((*this), "/streams/<string>/<string>")
.methods("GET"_method)
([this](const crow::request& req, crow::response& res, std::string stream_id, std::string filename) {
// CROW_ROUTE((*this), "/streams/<string>/<string>")
// .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();
// });
}