完成GStreamer工作流最小核心,采集视频数据
This commit is contained in:
parent
a4d926713f
commit
f42ee41573
|
|
@ -4,7 +4,11 @@
|
|||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${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": [
|
||||
"CROW_USE_BOOST"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ find_package(SQLite3 REQUIRED)
|
|||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0)
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
# 在包含 Crow 子目录之前,设置一个 CMake 选项
|
||||
# 这会告诉 Crow 的构建脚本去使用 Boost 库中包含的 Asio
|
||||
add_subdirectory(src/vendor/crow)
|
||||
|
|
@ -117,42 +116,23 @@ target_link_libraries(test PRIVATE
|
|||
)
|
||||
|
||||
|
||||
# 1. 定义新的可执行文件 (我们将把代码放在 src/streamer/ 目录下)
|
||||
# add_executable(edge_streamer
|
||||
# src/streamer/main.cpp
|
||||
# src/streamer/StreamManager.cpp
|
||||
# )
|
||||
add_executable(edge_streamer
|
||||
src/streamer/main.cpp
|
||||
)
|
||||
|
||||
# # 2. 为新服务链接所有必需的库
|
||||
# target_link_libraries(edge_streamer PRIVATE
|
||||
# # GStreamer (来自我们新增的 pkg_check_modules)
|
||||
# ${GST_LIBRARIES}
|
||||
# 链接新服务所需的所有库
|
||||
target_link_libraries(edge_streamer PRIVATE
|
||||
# --- GStreamer (来自 pkg_check_modules) ---
|
||||
${GST_LIBRARIES}
|
||||
|
||||
# Crow
|
||||
# nlohmann_json
|
||||
# PahoMqttCpp::paho-mqttpp3
|
||||
# Boost::system
|
||||
# Boost::thread
|
||||
# rknn_api
|
||||
# rknnrt
|
||||
# pthread
|
||||
# ssl
|
||||
# crypto
|
||||
# )
|
||||
pthread
|
||||
)
|
||||
|
||||
# # 3. 为新服务设置头文件包含路径
|
||||
# target_include_directories(edge_streamer PRIVATE
|
||||
# # 新服务的私有头文件
|
||||
# ${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
|
||||
# )
|
||||
# 为新服务设置头文件包含路径
|
||||
target_include_directories(edge_streamer PRIVATE
|
||||
# 新服务的私有头文件
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/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