完成GStreamer工作流最小核心,采集视频数据
This commit is contained in:
parent
a4d926713f
commit
f42ee41573
|
|
@ -4,7 +4,11 @@
|
||||||
"name": "Linux",
|
"name": "Linux",
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**",
|
"${workspaceFolder}/**",
|
||||||
"/usr/include"
|
"/usr/include",
|
||||||
|
"/usr/include/gstreamer-1.0",
|
||||||
|
"/usr/include/glib-2.0",
|
||||||
|
"/usr/lib/aarch64-linux-gnu/glib-2.0/include"
|
||||||
|
// (GStreamer 依赖 GLib, GLib的这个 .include 路径是必须的)
|
||||||
],
|
],
|
||||||
"defines": [
|
"defines": [
|
||||||
"CROW_USE_BOOST"
|
"CROW_USE_BOOST"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ find_package(SQLite3 REQUIRED)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0)
|
pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0)
|
||||||
find_package(OpenCV REQUIRED)
|
find_package(OpenCV REQUIRED)
|
||||||
|
|
||||||
# 在包含 Crow 子目录之前,设置一个 CMake 选项
|
# 在包含 Crow 子目录之前,设置一个 CMake 选项
|
||||||
# 这会告诉 Crow 的构建脚本去使用 Boost 库中包含的 Asio
|
# 这会告诉 Crow 的构建脚本去使用 Boost 库中包含的 Asio
|
||||||
add_subdirectory(src/vendor/crow)
|
add_subdirectory(src/vendor/crow)
|
||||||
|
|
@ -117,42 +116,23 @@ target_link_libraries(test PRIVATE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 1. 定义新的可执行文件 (我们将把代码放在 src/streamer/ 目录下)
|
add_executable(edge_streamer
|
||||||
# add_executable(edge_streamer
|
src/streamer/main.cpp
|
||||||
# src/streamer/main.cpp
|
)
|
||||||
# src/streamer/StreamManager.cpp
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # 2. 为新服务链接所有必需的库
|
# 链接新服务所需的所有库
|
||||||
# target_link_libraries(edge_streamer PRIVATE
|
target_link_libraries(edge_streamer PRIVATE
|
||||||
# # GStreamer (来自我们新增的 pkg_check_modules)
|
# --- GStreamer (来自 pkg_check_modules) ---
|
||||||
# ${GST_LIBRARIES}
|
${GST_LIBRARIES}
|
||||||
|
|
||||||
# Crow
|
pthread
|
||||||
# nlohmann_json
|
)
|
||||||
# PahoMqttCpp::paho-mqttpp3
|
|
||||||
# Boost::system
|
|
||||||
# Boost::thread
|
|
||||||
# rknn_api
|
|
||||||
# rknnrt
|
|
||||||
# pthread
|
|
||||||
# ssl
|
|
||||||
# crypto
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # 3. 为新服务设置头文件包含路径
|
# 为新服务设置头文件包含路径
|
||||||
# target_include_directories(edge_streamer PRIVATE
|
target_include_directories(edge_streamer PRIVATE
|
||||||
# # 新服务的私有头文件
|
# 新服务的私有头文件
|
||||||
# ${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include
|
${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include
|
||||||
|
|
||||||
# ${CMAKE_CURRENT_SOURCE_DIR}/src/vendor
|
|
||||||
|
|
||||||
# # GStreamer 的头文件
|
|
||||||
# ${GST_INCLUDE_DIRS}
|
|
||||||
|
|
||||||
# ${Boost_INCLUDE_DIRS}
|
|
||||||
|
|
||||||
# # RKNN C-API 的头文件 (来自我们 Dockerfile 的 /usr/local/include)
|
|
||||||
# /usr/local/include
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
# --- GStreamer (来自 pkg_check_modules) ---
|
||||||
|
${GST_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* 文件: src/streamer/main.cpp
|
||||||
|
* 目标: 阶段 0.1 - 最小硬件采集
|
||||||
|
* 职责: 连接RTSP, 使用MPP硬件解码, 并在C++回调中接收原始帧。
|
||||||
|
*/
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/app/gstappsink.h> // 包含 appsink 的头文件
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
// 这是一个简单的结构体,用于在回调间传递数据
|
||||||
|
typedef struct {
|
||||||
|
GMainLoop *loop;
|
||||||
|
GstElement *pipeline;
|
||||||
|
} AppData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 这是核心回调函数
|
||||||
|
* * 当 GStreamer 的 'appsink' 元件成功从管线中拉出一个解码后的帧时,
|
||||||
|
* 此函数将被调用。
|
||||||
|
* * @param appsink 触发信号的 appsink 元件
|
||||||
|
* @param user_data 我们传递的自定义数据
|
||||||
|
* @return GstFlowReturn 告诉管线上游是否继续发送数据
|
||||||
|
*/
|
||||||
|
static GstFlowReturn
|
||||||
|
on_new_sample (GstAppSink * appsink, gpointer user_data)
|
||||||
|
{
|
||||||
|
// 帧计数
|
||||||
|
static guint frame_count = 0;
|
||||||
|
|
||||||
|
// 从 appsink 中拉取 GstSample
|
||||||
|
// GstSample 是 GStreamer 中包含数据 (GstBuffer) 和元数据 (GstCaps) 的容器
|
||||||
|
GstSample *sample = gst_app_sink_pull_sample (appsink);
|
||||||
|
|
||||||
|
if (sample == NULL) {
|
||||||
|
g_warning ("Failed to pull sample.");
|
||||||
|
return GST_FLOW_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 核心采集点 ---
|
||||||
|
// 此时,'sample' 变量中就装着解码后的原始视频帧 (NV12 格式)
|
||||||
|
// 这是我们未来插入 ONNX/RGA/编码 的地方。
|
||||||
|
|
||||||
|
frame_count++;
|
||||||
|
|
||||||
|
if (frame_count % 100 == 0) {
|
||||||
|
g_print ("Hardware decoder acquired frame %u\n", frame_count);
|
||||||
|
|
||||||
|
// (可选的调试: 打印帧的详细信息)
|
||||||
|
// GstBuffer *buffer = gst_sample_get_buffer(sample);
|
||||||
|
// GstCaps *caps = gst_sample_get_caps(sample);
|
||||||
|
// g_print(" Buffer size: %lu, Caps: %s\n",
|
||||||
|
// gst_buffer_get_size(buffer),
|
||||||
|
// gst_caps_to_string(caps));
|
||||||
|
}
|
||||||
|
|
||||||
|
// **重要**: 释放 'sample'
|
||||||
|
// 必须释放它,否则将导致内存泄漏。
|
||||||
|
gst_sample_unref (sample);
|
||||||
|
|
||||||
|
return GST_FLOW_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
AppData data;
|
||||||
|
GstElement *appsink;
|
||||||
|
|
||||||
|
// 初始化 GStreamer 和 GMainLoop
|
||||||
|
gst_init (&argc, &argv);
|
||||||
|
data.loop = g_main_loop_new (NULL, FALSE);
|
||||||
|
|
||||||
|
// 构建采集管线 (Pipeline String)
|
||||||
|
// **注意**: 您的摄像头可能不是H.264,如果是H.265,请使用 rtph265depay ! h265parse
|
||||||
|
// **关键**: appsink 的配置
|
||||||
|
// name=sink: 给它一个名字 "sink",方便我们后面找到它
|
||||||
|
// emit-signals=true: **必须**设置为 true,它才会触发 'new-sample' 信号
|
||||||
|
// max-buffers=5: 设置一个小的内部缓冲区
|
||||||
|
// drop=true: 如果C++处理不过来 (缓冲区满了),就丢弃旧的帧 (防止内存溢出)
|
||||||
|
const gchar *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";
|
||||||
|
|
||||||
|
g_print ("Using pipeline:\n%s\n", pipeline_str);
|
||||||
|
|
||||||
|
data.pipeline = gst_parse_launch (pipeline_str, NULL);
|
||||||
|
if (!data.pipeline) {
|
||||||
|
g_printerr ("Failed to parse ingest pipeline.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 'appsink' 元件
|
||||||
|
// 我们通过在管线字符串中设置的 'name=sink' 来找到它
|
||||||
|
appsink = gst_bin_get_by_name (GST_BIN (data.pipeline), "sink");
|
||||||
|
if (!appsink) {
|
||||||
|
g_printerr("Failed to get appsink element.\n");
|
||||||
|
gst_object_unref(data.pipeline);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接 'new-sample' 信号到我们的 C++ 回调函数
|
||||||
|
// 这是整个程序最关键的“粘合”步骤
|
||||||
|
g_signal_connect (appsink, "new-sample", G_CALLBACK (on_new_sample), &data);
|
||||||
|
|
||||||
|
// appsink 已经设置好,我们可以释放对它的临时引用
|
||||||
|
g_object_unref (appsink);
|
||||||
|
|
||||||
|
// 启动管线
|
||||||
|
gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
|
||||||
|
g_print ("Acquisition pipeline started. Waiting for frames...\n");
|
||||||
|
|
||||||
|
// 运行主循环
|
||||||
|
// GStreamer 的所有工作都在后台线程中进行
|
||||||
|
// 主循环会阻塞在这里,直到 g_main_loop_quit() 被调用或程序被中断
|
||||||
|
g_main_loop_run (data.loop);
|
||||||
|
|
||||||
|
// 7. (程序退出时) 清理
|
||||||
|
g_print ("Exiting acquisition service...\n");
|
||||||
|
gst_element_set_state (data.pipeline, GST_STATE_NULL);
|
||||||
|
gst_object_unref (data.pipeline);
|
||||||
|
g_main_loop_unref (data.loop);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue