> 技术文档 > QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

        本文章介绍了Qt6.5完成了QT6.5使用第三方视频库GStreamer拉流显示的Demo程序,软件架构采用QML前端显示C++后端数据处理,目前程序在Windows与Android平台已验证成功。

        GStreamer采用官方预编译的库,库版本一定要选正确,一定要与本机的编译工具链版本匹配,否则会报出各种错误!

一、Windows GST安装

  1. Download GStreamer。
  2. 这里我选择Windows平台mingw编译版本,版本号为1.20.7,架构选择x86_64,同时下载Develop与Runtime版本msi,如下图。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  3. 两个msi下载完成之后全部双击安装,选择Typical经典安装,默认安装在D:/gstreamer路径。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  4. 安装工具默认已经默认将环境变量加到系统变量里。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  5. 将GSTREAMER_1_0_ROOT_MINGW_X86_64变量加到Path里面。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

二、Android GST安装

  1. Download GStreamer。
  2. 这里直接下载即可。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  3. 压缩包内包含安卓四个平台架构的库,解压后如下图。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  4. 将对应架构的文件夹添加到项目工程目录下(当然也可以将该目录添加到系统环境变量里)。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

三、Qt编译器配置

  • Qt 6.5.3 CMake编译
  • Windows编译器信息如下图。

QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  • Anroid编译器信息如下,我这里android sdk api版本为33。

QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

四、代码实现

1、创建QT Quick工程,在CMakeLists.txt加载GST库,并创建VideoStreamer类源与头文件用来拉取rtsp视频流。

cmake_minimum_required(VERSION 3.16)project(GSteamerQtDemo VERSION 0.1 LANGUAGES CXX)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_STANDARD 17)find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Multimedia)qt_standard_project_setup(REQUIRES 6.5)set(CMAKE_AUTORCC ON)qt_add_executable(appGSteamerQtDemo main.cpp videostreamer.h videostreamer.cpp)qt_add_qml_module(appGSteamerQtDemo URI GSteamerQtDemo VERSION 1.0 QML_FILES Main.qml)# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.# If you are developing for iOS or macOS you should consider setting an# explicit, fixed bundle identifier manually though.set_target_properties(appGSteamerQtDemo PROPERTIES # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appGSteamerQtDemo MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE)target_link_libraries(appGSteamerQtDemo PRIVATE Qt6::Quick PRIVATE Qt6::Multimedia)# 设置Android打包源目录set_property(TARGET appGSteamerQtDemo APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/android\")include(GNUInstallDirs)install(TARGETS appGSteamerQtDemo BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})# GStreamer配置if(ANDROID) # ANDROID # 设置Android的GStreamer路径 set(GSTREAMER_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/gstreamer/android/arm64) # 确保目录存在 if(NOT EXISTS \"${GSTREAMER_ANDROID_DIR}\") message(FATAL_ERROR \"GStreamer Android 目录不存在: ${GSTREAMER_ANDROID_DIR}\") endif() # 检查GStreamer版本兼容性 message(STATUS \"Android sdk version is 33 and ndk version is 25.1.8937393, please ensure GStreamer version is 1.20.7\") # 解决ffmpeg库汇编参数-fPIC报错问题 # 因为三方库中存在汇编编译的部分,所以需要修改CFLAGS参考如下,符号不可抢占且优先使用本地符号 set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wno-int-conversion -Wl,-Bsymbolic\") set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-int-conversion -Wl,-Bsymbolic\") # GStreamer头文件路径 include_directories(${GSTREAMER_ANDROID_DIR}/include) include_directories(${GSTREAMER_ANDROID_DIR}/include/glib-2.0) include_directories(${GSTREAMER_ANDROID_DIR}/include/gstreamer-1.0) include_directories(${GSTREAMER_ANDROID_DIR}/include/gstreamer-1.0/gst) include_directories(${GSTREAMER_ANDROID_DIR}/lib/glib-2.0/include) # GStreamer库文件 target_link_libraries(appGSteamerQtDemo PRIVATE \"${GSTREAMER_ANDROID_DIR}/lib/libgstreamer-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgobject-2.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libglib-2.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgmodule-2.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgio-2.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgthread-2.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libintl.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libiconv.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libffi.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstapp-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstvideo-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstbase-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/liborc-0.4.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstrtsp-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstsdp-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstrtp-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstnet-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstcodecparsers-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgsttag-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstaudio-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstpbutils-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libz.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstvideo-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstgl-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgraphene-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libjpeg.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libpng16.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstcontroller-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libgstphotography-1.0.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libopenh264.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libavcodec.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libavfilter.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libavformat.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libavutil.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libswresample.a\" \"${GSTREAMER_ANDROID_DIR}/lib/libbz2.a\" # 插件库 \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstcoreelements.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstrtsp.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstrtp.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstrtpmanager.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstvideoparsersbad.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstplayback.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstapp.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstvideoconvert.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstaudioconvert.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstaudioresample.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstvideotestsrc.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstandroidmedia.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstopengl.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstopenh264.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstjpeg.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstpng.a\" \"${GSTREAMER_ANDROID_DIR}/lib/gstreamer-1.0/libgstlibav.a\" # Android特定库 -lEGL -lGLESv2 -landroid ) # 添加编译定义 target_compile_definitions(appGSteamerQtDemo PRIVATE GST_STATIC_COMPILATION GST_DISABLE_GST_DEBUG ) elseif(MINGW) # MINGW # 尝试从环境变量获取 GStreamer 路径 if(DEFINED ENV{GSTREAMER_1_0_ROOT_MINGW_X86_64}) set(GSTREAMER_ROOT_DIR $ENV{GSTREAMER_1_0_ROOT_MINGW_X86_64}) message(STATUS \"use ENV GSTREAMER_1_0_ROOT_MINGW_X86_64: ${GSTREAMER_ROOT_DIR}\") else() # 尝试常见的安装路径 set(GSTREAMER_ROOT_DIR \"D:/Program Files/gstreamer/1.0/mingw_x86_64\") message(WARNING \"not found GSTREAMER_1_0_ROOT_MINGW_X86_64 ENV, use default: ${GSTREAMER_ROOT_DIR}\") endif() # 验证路径是否存在 if(NOT EXISTS \"${GSTREAMER_ROOT_DIR}\") message(FATAL_ERROR \"GStreamer is not existed: ${GSTREAMER_ROOT_DIR}\") endif() # 检查GStreamer版本兼容性 message(STATUS \"Qt MinGW version is GCC 11.2.0, please ensure GStreamer version is 1.20.7\") # GStreamer头文件路径 include_directories(\"${GSTREAMER_ROOT_DIR}/include\") include_directories(\"${GSTREAMER_ROOT_DIR}/include/glib-2.0\") include_directories(\"${GSTREAMER_ROOT_DIR}/include/gstreamer-1.0\") include_directories(\"${GSTREAMER_ROOT_DIR}/include/gstreamer-1.0/gst\") include_directories(\"${GSTREAMER_ROOT_DIR}/lib/glib-2.0/include\") # GStreamer库文件 target_link_libraries(appGSteamerQtDemo PRIVATE \"${GSTREAMER_ROOT_DIR}/lib/gstreamer-1.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gobject-2.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/glib-2.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gmodule-2.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gio-2.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gthread-2.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/intl.lib\" \"${GSTREAMER_ROOT_DIR}/lib/iconv.lib\" \"${GSTREAMER_ROOT_DIR}/lib/ffi.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gstapp-1.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gstvideo-1.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/gstbase-1.0.lib\" \"${GSTREAMER_ROOT_DIR}/lib/orc-0.4.lib\" )endif()

2、AndroidManifest.xml配置安卓包参数。

                  

3、videostreamer.h文件实现,使用QVideoSink作为视频流接收器。

#ifndef VIDEOSTREAMER_H#define VIDEOSTREAMER_H#include #include #include #include #include #include class VideoStreamer : public QObject{ Q_OBJECT public: explicit VideoStreamer(QObject* parent = nullptr); ~VideoStreamer(); Q_INVOKABLE void setVideoSink(QVideoSink* sink); Q_INVOKABLE void start(const QString& rtspUrl); Q_INVOKABLE void stop(); private: GstElement* pipeline = nullptr; QVideoSink* m_sink = nullptr; static GstFlowReturn onNewSample(GstElement* sink, VideoStreamer* self); static void busCallback(GstBus* bus, GstMessage* message, VideoStreamer* self);};class GlibMainLoopThread : public QThread{ public: void run() override { GMainLoop* loop = g_main_loop_new(nullptr, FALSE); g_main_loop_run(loop); g_main_loop_unref(loop); }};#endif // VIDEOSTREAMER_H

4、videostreamer.cpp文件实现,调用start方法开启管道,成功连接url后,自动调用onNewSample回调函数拉取视频流,在该函数内将视频帧QVideoFrame通过QVideoSink的setVideoFrame方法传给qml前端,qml内的VideoOutput组件再将该视频帧进行视频渲染显示。

#include \"videostreamer.h\"#include #include #include #include #include #include // 帮助打印GStreamer管道结构的辅助类class GstPrint{ public: GstPrint() : indent(0) {} void printElement(GstElement* element, int depth) { indent = depth; printIndent(); qDebug() << \"Element:\" << GST_ELEMENT_NAME(element)  << \"Type:\" << G_OBJECT_TYPE_NAME(element); // 如果是容器,递归打印其子元素 if (GST_IS_BIN(element)) { GstBin* bin = GST_BIN(element); GstElement* child = nullptr; GstIterator* it = gst_bin_iterate_elements(bin); GValue item = G_VALUE_INIT; while (gst_iterator_next(it, &item) == GST_ITERATOR_OK) { child = GST_ELEMENT(g_value_get_object(&item)); printElement(child, depth + 1); g_value_reset(&item); } gst_iterator_free(it); } } private: void printIndent() { QString spaces; for (int i = 0; i < indent; i++) { spaces += \" \"; } QDebug(QtDebugMsg) <next) { GstElementFactory* factory = GST_ELEMENT_FACTORY(f->data); qDebug() << \"factory name:\" << gst_element_get_name(factory); } gst_plugin_feature_list_free(factories);}#endifVideoStreamer::VideoStreamer(QObject* parent) : QObject(parent){ gst_init(nullptr, nullptr);#ifdef Q_OS_ANDROID // 安卓设备软件链接静态库需要手动注册插件 gst_android_register_static_plugins(); // 列出已注册的插件库中的所有元件 list_available_elements();#endif}VideoStreamer::~VideoStreamer(){ if (pipeline) { gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); }}/** * @brief VideoStreamer::setVideoSink 设置视频流接收器 * @param sink */void VideoStreamer::setVideoSink(QVideoSink* sink){ m_sink = sink; qDebug() << \"设置VideoSink:\" << (m_sink != nullptr ? \"成功\" : \"失败\");}/** * @brief VideoStreamer::start 开始配置管道 * @param rtspUrl */void VideoStreamer::start(const QString& rtspUrl){ QString pipelineStr = \"\"; if (pipeline) { gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); pipeline = nullptr; }#ifdef Q_OS_ANDROID pipelineStr = QString(\"rtspsrc location=%1 latency=0 buffer-mode=none protocols=tcp ! \" \"rtph264depay ! h264parse ! avdec_h264 ! \" \"videoconvert ! video/x-raw,format=NV12 ! \" \"appsink name=qt-sink emit-signals=true sync=false max-buffers=2 drop=true\") .arg(rtspUrl);#endif#ifdef Q_OS_WIN pipelineStr = QString(\"rtspsrc location=%1 latency=0 buffer-mode=none protocols=tcp ! \" \"rtph264depay ! h264parse ! decodebin ! \" \"videoconvert ! video/x-raw,format=NV12 ! \" \"appsink name=qt-sink emit-signals=true sync=false max-buffers=2 drop=true\") .arg(rtspUrl);#endif // 本地测试流,若测试流显示没有问题则证明管道本身没有问题 if (rtspUrl == \"test:pattern\") { pipelineStr = \"videotestsrc pattern=0 ! \"\"videoconvert ! video/x-raw,format=BGRA ! \"\"appsink name=qt-sink emit-signals=true sync=false\"; } // 创建管道 GError* error = nullptr; pipeline = gst_parse_launch(pipelineStr.toUtf8(), &error); if (error) { qWarning() << \"创建管道失败:\" <message; g_error_free(error); return; } // 打印管道结构-调试打印 if (pipeline) { qDebug() << \"管道创建成功, 打印结构...\"; GstPrint printer; gst_print_element_info(pipeline, printer); } // 获取接收器appsink GstElement* sink = gst_bin_get_by_name(GST_BIN(pipeline), \"qt-sink\"); if (!sink) { qWarning() << \"无法获取appsink元素\"; gst_object_unref(pipeline); pipeline = nullptr; return; } // 设置appsink属性 gst_app_sink_set_emit_signals(GST_APP_SINK(sink), TRUE); gst_app_sink_set_drop(GST_APP_SINK(sink), TRUE); // 允许丢弃旧帧 gst_app_sink_set_max_buffers(GST_APP_SINK(sink), 2); // 只保留最新的几帧 // 检查设置的属性 gboolean emit_signals = FALSE; g_object_get(sink, \"emit-signals\", &emit_signals, NULL); qDebug() << \"appsink属性设置: emit-signals=\" << (emit_signals ? \"TRUE\" : \"FALSE\") << \"drop=\" << (gst_app_sink_get_drop(GST_APP_SINK(sink)) ? \"TRUE\" : \"FALSE\") << \"max-buffers=\" < 0) { qDebug() << \"new-sample信号连接成功, ID =\" << handler_id; } else { qWarning() << \"无法连接new-sample信号!\"; } gst_object_unref(sink); qDebug() << \"设置管道状态为PLAYING\"; GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { qWarning() << \"无法启动GStreamer管道\"; gst_object_unref(pipeline); pipeline = nullptr; } else if (ret == GST_STATE_CHANGE_ASYNC || ret == GST_STATE_CHANGE_NO_PREROLL) { qDebug() << \"管道状态异步更改中\"; // // 调试用 // // 加长等待时间 // GstState state; // ret = gst_element_get_state(pipeline, &state, NULL, 5 * GST_SECOND); // qDebug() << \"等待状态变化结果:\" << ret << \"当前状态:\" << // gst_element_state_get_name(state); // // 如果依然是PAUSED状态,尝试强制转换到PLAYING // if (state == GST_STATE_PAUSED) { // qDebug() << \"尝试强制切换到PLAYING状态\"; // ret = gst_element_set_state(pipeline, GST_STATE_PLAYING); // ret = gst_element_get_state(pipeline, &state, NULL, 5 * GST_SECOND); // qDebug() << \"再次等待结果:\" << ret << \"当前状态:\" << // gst_element_state_get_name(state); // } } else if (ret == GST_STATE_CHANGE_SUCCESS) { qDebug() << \"管道成功启动\"; }}GstFlowReturn VideoStreamer::onNewSample(GstElement* sink, VideoStreamer* self){ // qDebug() << \"onNewSample thread id:\" << QThread::currentThreadId(); static int frameCount = 0; frameCount++; if (!self) { qWarning() <m_sink) { qWarning() << \"onNewSample: QVideoSink未设置!\"; return GST_FLOW_ERROR; } GstSample* sample = gst_app_sink_pull_sample(GST_APP_SINK(sink)); if (!sample) { qWarning() << \"onNewSample: 无法获取样本!\"; return GST_FLOW_ERROR; } // qDebug() << \"成功获取GstSample\"; GstBuffer* buffer = gst_sample_get_buffer(sample); if (!buffer) { qWarning() << \"onNewSample: 无法获取buffer!\"; gst_sample_unref(sample); return GST_FLOW_ERROR; } // 获取buffer的时间戳 GstClockTime pts = GST_BUFFER_PTS(buffer); GstClockTime dts = GST_BUFFER_DTS(buffer); GstClockTime duration = GST_BUFFER_DURATION(buffer); // 获取当前系统时间 GstClockTime current_time = gst_clock_get_time(gst_system_clock_obtain()); // 计算延迟 GstClockTime latency = current_time - pts; GstCaps* caps = gst_sample_get_caps(sample); if (!caps) { qWarning() << \"onNewSample: 无法获取caps!\"; gst_sample_unref(sample); return GST_FLOW_ERROR; } // 打印caps内容 gchar* capsStr = gst_caps_to_string(caps); // qDebug() << \"视频帧caps:\" << capsStr; g_free(capsStr); GstVideoInfo info; gst_video_info_init(&info); if (!gst_video_info_from_caps(&info, caps)) { qWarning() << \"onNewSample: 无法从caps创建视频信息!\"; gst_sample_unref(sample); return GST_FLOW_ERROR; } GstVideoFrame gst_frame; if (gst_video_frame_map(&gst_frame, &info, buffer, GST_MAP_READ)) { // 根据GStreamer格式确定Qt视频格式 QVideoFrameFormat::PixelFormat pixelFormat; // 输出视频格式信息 // qDebug() << \"GStreamer视频格式:\" << GST_VIDEO_INFO_FORMAT(&info) // << \"宽度:\" << GST_VIDEO_INFO_WIDTH(&info) // << \"高度:\" << GST_VIDEO_INFO_HEIGHT(&info); // 根据GStreamer格式确定QVideoFrameFormat switch (GST_VIDEO_INFO_FORMAT(&info)) { case GST_VIDEO_FORMAT_BGRA: pixelFormat = QVideoFrameFormat::Format_BGRA8888; break; case GST_VIDEO_FORMAT_RGBA: pixelFormat = QVideoFrameFormat::Format_RGBA8888; break; case GST_VIDEO_FORMAT_YUY2: pixelFormat = QVideoFrameFormat::Format_YUYV; break; case GST_VIDEO_FORMAT_I420: pixelFormat = QVideoFrameFormat::Format_YUV420P; break; case GST_VIDEO_FORMAT_NV12: pixelFormat = QVideoFrameFormat::Format_NV12; break; default: qWarning() << \"不支持的视频格式:\" << GST_VIDEO_INFO_FORMAT(&info); gst_video_frame_unmap(&gst_frame); gst_sample_unref(sample); return GST_FLOW_ERROR; } int width = GST_VIDEO_INFO_WIDTH(&info); int height = GST_VIDEO_INFO_HEIGHT(&info); // qDebug() << \"接收到视频帧:\" << width << \"x\" << height; // 创建适当格式的视频帧 QVideoFrameFormat format(QSize(width, height), pixelFormat); QVideoFrame videoFrame(format); if (videoFrame.map(QVideoFrame::WriteOnly)) { // 获取源和目标数据指针 uchar* srcData = (uchar*)GST_VIDEO_FRAME_PLANE_DATA(&gst_frame, 0); uchar* dstData = videoFrame.bits(0); if (!srcData) { qWarning() << \"源数据指针为空!\"; videoFrame.unmap(); gst_video_frame_unmap(&gst_frame); gst_sample_unref(sample); return GST_FLOW_ERROR; } if (!dstData) { qWarning() << \"目标数据指针为空!\"; videoFrame.unmap(); gst_video_frame_unmap(&gst_frame); gst_sample_unref(sample); return GST_FLOW_ERROR; } // 计算数据大小并复制 int dataSize = GST_VIDEO_FRAME_SIZE(&gst_frame); // qDebug() << \"复制数据大小:\" << dataSize << \"字节\"; memcpy(dstData, srcData, dataSize); // 解锁映射 videoFrame.unmap(); // qDebug() <m_sink->setVideoFrame(videoFrame); // qDebug() << \"视频帧已发送到QVideoSink - 帧\" << frameCount; } else { qWarning() << \"无法映射QVideoFrame!\"; } gst_video_frame_unmap(&gst_frame); } else { qWarning() << \"无法映射GStreamer视频帧!\"; } gst_sample_unref(sample); return GST_FLOW_OK;}void VideoStreamer::stop(){ if (pipeline) { gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); pipeline = nullptr; }}void VideoStreamer::busCallback(GstBus* bus, GstMessage* message, VideoStreamer* self){ switch (GST_MESSAGE_TYPE(message)) { case GST_MESSAGE_ERROR: { GError* error = nullptr; gchar* debug = nullptr; gst_message_parse_error(message, &error, &debug); qWarning() << \"GStreamer错误:\" <message; qWarning() << \"调试信息:\" << debug; g_free(debug); g_error_free(error); break; } case GST_MESSAGE_WARNING: { GError* warning = nullptr; gchar* debug = nullptr; gst_message_parse_warning(message, &warning, &debug); qWarning() << \"GStreamer警告:\" <message; qWarning() << \"调试信息:\" << debug; g_free(debug); g_error_free(warning); break; } case GST_MESSAGE_INFO: { GError* info = nullptr; gchar* debug = nullptr; gst_message_parse_info(message, &info, &debug); qDebug() << \"GStreamer信息:\" <message; qDebug() << \"调试信息:\" << debug; g_free(debug); g_error_free(info); break; } case GST_MESSAGE_EOS: qDebug() <pipeline)) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state); qDebug() << \"GStreamer管道状态变化:\" << gst_element_state_get_name(old_state) < \" << gst_element_state_get_name(new_state) << \" (pending:\" << gst_element_state_get_name(pending_state) <pipeline), \"qt-sink\");  if (sink) { qDebug() << \"找到appsink,检查信号连接...\"; bool connected = g_signal_handler_find( sink, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, (void*)onNewSample, self); qDebug() << \"onNewSample连接状态:\" << (connected ? \"已连接\" : \"未连接\"); // 检查appsink属性 gboolean emit_signals = FALSE; g_object_get(sink, \"emit-signals\", &emit_signals, NULL); qDebug() << \"appsink emit-signals属性:\"  << (emit_signals ? \"TRUE\" : \"FALSE\"); gst_object_unref(sink);  } else { qWarning() << \"无法在PLAYING状态找到appsink\";  } } } break; case GST_MESSAGE_STREAM_STATUS: { GstStreamStatusType type; GstElement* owner = nullptr; gst_message_parse_stream_status(message, &type, &owner); qDebug() << \"流状态更新:\" << type  << \"元素:\" << (owner ? GST_ELEMENT_NAME(owner) : \"未知\"); } break; default: qDebug() << \"GStreamer消息:\" << GST_MESSAGE_TYPE_NAME(message); break; }}

5、main.cpp,设置VideoStreamer上下文属性,用于与qml数据交互。

#include #include #include #include \"videostreamer.h\"int main(int argc, char* argv[]){ // qputenv(\"QT_IM_MODULE\", QByteArray(\"qtvirtualkeyboard\")); QGuiApplication app(argc, argv); // 注册视频流控制类 qmlRegisterType(\"VideoStreamer\", 1, 0, \"VideoStreamer\"); QQmlApplicationEngine engine; VideoStreamer streamer; engine.rootContext()->setContextProperty(\"VideoStreamer\", &streamer); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule(\"GSteamerQtDemo\", \"Main\");#ifdef Q_OS_ANDROID static GlibMainLoopThread glibLoopThread; // glibLoopThread.start();#endif return app.exec();}

5、main.qml,将VideoStreamer与VideoOutput的videosink绑定,调用VideoStreamer的start方法将url传到后端拉取视频流。

import QtQuickimport QtQuick.VirtualKeyboardimport QtMultimediaimport QtQuick.Controlsimport QtQuick.LayoutsWindow { id: window width: 640 height: 480 visible: true title: qsTr(\"GStreamer RTSP流演示\") Component.onCompleted: { console.log(\"主窗口初始化完成\") console.log(\"设置VideoSink...\") if (videoOutput && videoOutput.videoSink) { console.log(\"VideoSink可用,正在设置给VideoStreamer\") VideoStreamer.setVideoSink(videoOutput.videoSink) statusText.text = \"视频输出已初始化\" } else { console.error(\"VideoSink不可用!\") statusText.text = \"错误:视频输出初始化失败\" } } Rectangle { id: playerContainer anchors.fill: parent color: \"black\" // 视频输出组件 VideoOutput { id: videoOutput anchors.fill: parent fillMode: VideoOutput.PreserveAspectFit visible: true // 设为可见 Component.onCompleted: { console.log(\"VideoOutput已创建,videoSink:\", videoOutput.videoSink ? \"可用\" : \"不可用\") } Rectangle { // 仅用于调试 - 显示视频输出区域边框 color: \"transparent\" border.color: \"red\" border.width: 2 anchors.fill: parent visible: true // 设为false可隐藏 } } ColumnLayout { anchors { left: parent.left right: parent.right bottom: parent.bottom margins: 10 } TextField { id: rtspUrlInput Layout.fillWidth: true placeholderText: \"输入RTSP URL\" //text: \"rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream\" //text: \"https://gstreamer.freedesktop.org/media/sintel_trailer-480p.webm\" //text: \"rtsp://rtspstream:qrDIMnuNzPL2cwHIg0HRA@zephyr.rtsp.stream/movie\" text: \"rtsp://admin:admin@192.168.10.41:8554/live\" color: \"black\" background: Rectangle {  color: \"white\" } } RowLayout { Button {  text: \"开始播放\"  Layout.fillWidth: true  onClicked: { console.log(\"开始播放URL:\", rtspUrlInput.text) VideoStreamer.start(rtspUrlInput.text)  } } Button {  text: \"停止\"  Layout.fillWidth: true  onClicked: { console.log(\"停止播放\") VideoStreamer.stop()  } } Button {  text: \"测试图案\"  Layout.fillWidth: true  onClicked: { console.log(\"播放测试图案\") VideoStreamer.start(\"test:pattern\")  } } } // 状态显示文本 Text { id: statusText Layout.fillWidth: true text: \"等待开始播放\" color: \"white\" horizontalAlignment: Text.AlignHCenter } } }}

五、程序调试与实现效果

  • 先测试本地管道测试流,点击“测试图案”按钮播放测试流,若能正常显示则表明库引用成功,管道本身功能正常。

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  • 使用第三方测试rtsp视频流进行调试,下面为示例。
  1. rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  2. rtsp://rtspstream:qrDIMnuNzPL2cwHIg0HRA@zephyr.rtsp.stream/movie

    QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

  • 使用第三方IP摄像头服务软件,如IP摄像头Lite,这里以该软件为例,手机与PC连接同一个局域网后,打开该软件,点击“打开IP摄像头服务器”,加上用户名与密码就是url,例如rtsp://admin:admin@192.168.10.41:8554/live。

QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库grapheneQT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库grapheneQT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

苹果手机的wifi设置里,一定要关闭“私有无限局域网地址”与“限制IP追踪”,并保证在PC端能ping通手机端IP,下面为效果(手机端截图,后摄拍PC)。

QT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

六、问题总结

  1. 选择的官方预编译库的版本,一定要与本地的编译工具链版本匹配,否则会报各种错误,建议按照官方推荐版本安装,不行再尝试安装低一版本的库。
  2. 安卓版本下载的是静态库,在链接静态库时需要在代码里手动注册插件,要在gst_init之后注册,运行时报错找不到元素(elements)时,同样需要如下所示注册元素所在的插件,并在CMakeLists.txt里链接插件库与相关依赖库。
     GST_PLUGIN_STATIC_DECLARE(coreelements); GST_PLUGIN_STATIC_DECLARE(rtsp); GST_PLUGIN_STATIC_DECLARE(rtp); GST_PLUGIN_STATIC_DECLARE(rtpmanager); GST_PLUGIN_STATIC_REGISTER(coreelements); GST_PLUGIN_STATIC_REGISTER(rtsp); GST_PLUGIN_STATIC_REGISTER(rtp); GST_PLUGIN_STATIC_REGISTER(rtpmanager);
  3. 编译报错如::-1: error: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol \'ff_cos_32\'; recompile with -fPICQT6 QML GStreamer (Windows+Android)显示RTSP视频流_libgstopengl.a缺乏依赖库graphene

解决方法:在CMakeLists.txt内增加如下配置。

# 解决ffmpeg库汇编参数-fPIC报错问题# 因为三方库中存在汇编编译的部分,所以需要修改CFLAGS参考如下,符号不可抢占且优先使用本地符号set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wno-int-conversion -Wl,-Bsymbolic\")set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-int-conversion -Wl,-Bsymbolic\")