求助:GStreamer获取RTSP流帧存入OpenCV Mat时Appsink取样本报错
解决GStreamer Appsink获取RTSP帧并转换为OpenCV Mat的问题
嘿,我来帮你搞定从GStreamer的appsink里拿帧转成OpenCV Mat的事儿!你说的获取样本出错,大概率是没正确处理GstSample和GstBuffer的流程,或者appsink的配置没跟上。下面我一步步给你讲正确的做法:
关键注意点
- 首先得给appsink设置匹配的Caps,确保输出的帧格式是OpenCV能直接处理的(比如BGR,因为OpenCV默认用BGR通道顺序)
- 获取样本后要正确处理buffer的内存映射,用完记得释放资源,不然会内存泄漏或者崩溃
完整示例代码
#include <gst/gst.h> #include <opencv2/opencv.hpp> #include <gst/app/gstappsink.h> // 注意正确的头文件,旧版本可能是gstappsink.h static GstFlowReturn new_sample_callback(GstAppSink *sink, gpointer user_data) { cv::Mat *frame = static_cast<cv::Mat*>(user_data); GstSample *sample = gst_app_sink_pull_sample(sink); if (!sample) { g_printerr("Failed to pull sample from appsink\n"); return GST_FLOW_ERROR; } // 1. 从sample中获取buffer GstBuffer *buffer = gst_sample_get_buffer(sample); if (!buffer) { g_printerr("Failed to get buffer from sample\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } // 2. 获取buffer的caps,提取宽高和格式信息 GstCaps *caps = gst_sample_get_caps(sample); GstStructure *structure = gst_caps_get_structure(caps, 0); gint width, height; if (!gst_structure_get_int(structure, "width", &width) || !gst_structure_get_int(structure, "height", &height)) { g_printerr("Failed to get frame dimensions\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } // 3. 映射buffer内存到可访问的区域 GstMapInfo map_info; if (!gst_buffer_map(buffer, &map_info, GST_MAP_READ)) { g_printerr("Failed to map buffer\n"); gst_sample_unref(sample); return GST_FLOW_ERROR; } // 4. 将映射的数据转换为OpenCV Mat // 这里假设我们配置的caps是BGR格式,比如video/x-raw, format=BGR, width=..., height=... *frame = cv::Mat(cv::Size(width, height), CV_8UC3, map_info.data, cv::Mat::AUTO_STEP); // 如果是RGB格式,需要转成BGR:cv::cvtColor(rgb_mat, bgr_mat, cv::COLOR_RGB2BGR); // 5. 解除内存映射,释放sample资源 gst_buffer_unmap(buffer, &map_info); gst_sample_unref(sample); // 这里可以添加帧处理逻辑,比如显示帧 cv::imshow("RTSP Stream", *frame); if (cv::waitKey(1) == 'q') { // 可以在这里设置退出标志 } return GST_FLOW_OK; } int main(int argc, char *argv[]) { gst_init(&argc, &argv); cv::namedWindow("RTSP Stream", cv::WINDOW_AUTOSIZE); cv::Mat frame; // 构建GStreamer管道:RTSP源 -> 解码 -> 格式转换 -> appsink // 替换成你的RTSP地址 gchar *pipeline_str = g_strdup_printf( "rtspsrc location=rtsp://your_rtsp_url_here ! rtph264depay ! h264parse ! avdec_h264 ! " "videoconvert ! video/x-raw, format=BGR ! appsink name=sink" ); GstElement *pipeline = gst_parse_launch(pipeline_str, NULL); if (!pipeline) { g_printerr("Failed to create pipeline\n"); return -1; } g_free(pipeline_str); // 获取appsink元素,设置回调和属性 GstElement *appsink = gst_bin_get_by_name(GST_BIN(pipeline), "sink"); gst_app_sink_set_emit_signals(GST_APP_SINK(appsink), TRUE); gst_app_sink_set_drop(GST_APP_SINK(appsink), FALSE); gst_app_sink_set_max_buffers(GST_APP_SINK(appsink), 1); g_signal_connect(appsink, "new-sample", G_CALLBACK(new_sample_callback), &frame); // 启动管道 gst_element_set_state(pipeline, GST_STATE_PLAYING); // 主循环,直到用户按下q while (true) { if (cv::waitKey(1) == 'q') { break; } // 处理GStreamer的消息,避免阻塞 GstBus *bus = gst_element_get_bus(pipeline); GstMessage *msg = gst_bus_timed_pop(bus, GST_CLOCK_TIME_NONE); if (msg) { gst_message_unref(msg); } gst_object_unref(bus); } // 清理资源 gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); cv::destroyAllWindows(); return 0; }
核心步骤解释(就是你标粗的获取样本环节)
- 获取GstSample:用
gst_app_sink_pull_sample从appsink拉取样本,一定要检查返回值是否为NULL,避免空指针崩溃 - 提取GstBuffer:样本里的实际帧数据存在buffer中,用
gst_sample_get_buffer获取 - 获取帧信息:从caps里提取宽高和格式,确保和OpenCV Mat的参数匹配
- 映射内存:用
gst_buffer_map把buffer的内存映射到用户空间,这样才能直接读取数据 - 转换为Mat:根据宽高、通道数(BGR是3通道)创建Mat,直接用映射的内存地址作为数据指针(注意格式要对应)
- 释放资源:一定要解除内存映射,并且调用
gst_sample_unref释放样本,否则会内存泄漏
编译命令
记得编译时链接GStreamer和OpenCV的库,比如:
g++ -o rtsp_opencv rtsp_opencv.cpp `pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 opencv4`
如果你的系统是GStreamer 0.10版本,需要调整头文件和pkg-config的参数,但推荐用1.0版本。
内容的提问来源于stack exchange,提问作者bsonbudak




