使用QT实现播放gstreamer的命令(二)

news2024/11/16 1:36:33

 一、前言

      上一篇文章写到了,如何快速使用C++来执行gstreamer的命令,如何在QT中显示gstreamer的画面,原文如下:

        https://blog.csdn.net/Alon1787/article/details/135107958

二、近期的其他发现:

1.gstreamer的画面显示在QT界面,使用的是绑定overlay

Demo1:使用QT界面显示gstreamer的命令:

#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget window;
    WId window_handle;

    gst_init(&argc, &argv);

    // 创建管道 pipeline
    //GstElement *pipeline = gst_parse_launch("filesrc name=mysrc ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw, width=640,height=480 ! ximagesink name=vsink", NULL);
    GstElement *pipeline = gst_parse_launch("videotestsrc ! ximagesink name=vsink",NULL);

    // 设置管道中的属性(创建管道的时候,使用第一条才有效)
    GstElement *mysrc = gst_bin_get_by_name (GST_BIN (pipeline), "mysrc");
    g_object_set (mysrc, "location", "/home/enpht/Videos/1081.mp4", NULL);
    g_object_unref (mysrc);

    // 创建界面
    window.show();
    window_handle = window.winId();

    // 链接到QT:
    GstElement *vsink = gst_bin_get_by_name (GST_BIN (pipeline), "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);

    // Start playing
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // Run the QT application loop
    int ret = app.exec();

    // Clean up
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return ret;
}

2. 并不是所有的sink插件,都支持overlay:

        经过测试,很多sink插件都无法正常的使用QT界面来显示,所以常用的是ximagesink、xvimagesink、glimagesink。

        以下是我的一些其他输出插件的总结:

常用输出插件:(一般在结尾处)
  • glimagesink、基于OpenGL或者OpenGL ES

  • autovideosink 自动选择显示

Linux:
  • xvimagesink 测试用的显示图片的插件,可直接显示rgb,不支持YUV. 需要VX支持,支持Xoverlay和fps

  • ximagesink、元素使用X系统来显示视频,支持Xoverlay 。支持Xoverlay,但是overlay不支持fpsdisplay

  • alsasink 非常简单的音频输出接口

  • pulsesink 高级一点的音频输出接口,但是旧Linux上不稳定

Mac OS X:
  • osxvideosink 唯一提供的视频输出

  • osxaudiosink 唯一提供的音频输出

windows:
  • d3d11videosink 基于Direct3D11,是最好用性能最高的输出,支持overlay,但有些版本没有

  • d3dvideosink 基于Direct3D9,,支持overlay,不建议在win8以上系统

Android:
  • openslessink 唯一可用的视频输出

  • openslessrc 唯一可用的音频输出

  • androidmedia 安卓自带的解码

  • ahcsrc 安卓的摄像头采集

IOS:
  • osxaudiosink 唯一可用音频接收

  • iosassetsrc 读取IOS内容

  • iosavassetsrc 读取IOS内容

其他:
  • waylandsink 元素使用Wayland显示协议来显示视频,不建议使用

  • filesink 文件输出 如filesink location=/home/enpht/Pictures/YUV_test

  • fpsdisplaysink 可以打印帧率等信息 默认autovideosink,可以设置video-sink来设置选用哪个实际输出

  • fakevideosink 调试用

  • gtksink 自带的GUI显示

  • gtkglsink 自带的OpenGL的GUI显示

  • clutterautovideosink 使用clutter库实现播放,一般不用

  • aasink 用ascii字符显示视频画面

  • cacasink 用ascii字符显示视频画面,彩色

QT中可以使用,但是命令行用不了:
  • qmlglsink 使用qml

  • qtvideosink painting on any surface with QPainter

  • qtglvideosink painting on any surface with QPainter and OpenGL

  • qtquick2videosink 貌似没用,仅限QtQuick2

  • qwidgetvideosink painting on QWidgets

3. 使用fpsdisplaysink可以通过设置video-sink的属性来显示画面和帧率

命令行:

gst-launch-1.0 -v udpsrc port=10010 ! capsfilter caps="application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)RAW, sampling=(string)YCbCr-4:2:0, depth=(string)8, width=(string)1920, height=(string)1080, colorimetry=(string)SMPTE240M, payload=(int)96, a-framerate=(string)30" ! queue ! rtpvrawdepay ! videoconvert ! fpsdisplaysink video-sink=xvimagesink

改为QT中的C++代码,QT显示RTP视频,同时显示帧率

#include <QApplication>
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

int main(int argc, char *argv[]) {

    QApplication a(argc, argv);

    GstElement *pipeline, *udpsrc, *capsfilter, *queue, *rtpvrawdepay, *jitterbuffer, *videoconvert, *vsink, *fpssink;
    GstCaps *caps;

    GstStateChangeReturn ret;

    QWidget *window = new QWidget();
    window->resize(1920, 1080);
    window->show();
    WId xwinid = window->winId();

    // 初始化 GStreamer
    gst_init(NULL, NULL);

    // 创建元素
    pipeline = gst_pipeline_new("my-pipeline");
    udpsrc = gst_element_factory_make("udpsrc", "udpsrc");
    capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
    queue = gst_element_factory_make("queue", "queue");
    jitterbuffer = gst_element_factory_make ("rtpjitterbuffer", "jitterbuffer");
    rtpvrawdepay = gst_element_factory_make("rtpvrawdepay", "rtpvrawdepay");
    videoconvert = gst_element_factory_make("videoconvert", "videoconvert");
    fpssink = gst_element_factory_make("fpsdisplaysink", "fpssink");
    vsink = gst_element_factory_make("xvimagesink", "vsink");//glimagesink

    if (!pipeline || !udpsrc || !capsfilter || !queue || !rtpvrawdepay || !videoconvert || !fpssink || !vsink) {
        g_printerr("Failed to create elements. Exiting.\n");
        return -1;
    }

    // 设置 udpsrc 元素的参数
    g_object_set(udpsrc, "port", 10010, NULL);

    // 创建 caps
    caps = gst_caps_new_simple("application/x-rtp",
                               "media", G_TYPE_STRING, "video",
                               "clock-rate", G_TYPE_INT, 90000,
                               "encoding-name", G_TYPE_STRING, "RAW",
                               "sampling", G_TYPE_STRING, "YCbCr-4:2:0",
                               "depth", G_TYPE_STRING, "8",
                               "width", G_TYPE_STRING, "1920",
                               "height", G_TYPE_STRING, "1080",
                               "colorimetry", G_TYPE_STRING, "SMPTE240M",
                               "payload", G_TYPE_INT, 96,
                               "a-framerate", G_TYPE_STRING, "30",
                               NULL);
    g_object_set(capsfilter, "caps", caps, NULL);
    gst_caps_unref(caps);

    g_object_set (jitterbuffer, "latency", 20, "do-lost", TRUE, "do-retransmission", TRUE, NULL);
    g_object_set (fpssink, "video-sink", vsink, NULL);

    // 将元素添加到管道中
    gst_bin_add_many(GST_BIN(pipeline), udpsrc, capsfilter, queue, jitterbuffer, rtpvrawdepay, videoconvert, fpssink, NULL);

    // 连接元素
    if (!gst_element_link_many(udpsrc, capsfilter, queue, jitterbuffer, rtpvrawdepay, videoconvert, fpssink, NULL)) {
        g_printerr("Failed to link elements. Exiting.\n");
        gst_object_unref(pipeline);
        return -1;
    }

    // 链接QT界面
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), xwinid);

    // 设置管道状态为播放
    ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr("Failed to set pipeline state to PLAYING. Exiting.\n");
        gst_object_unref(pipeline);
        return -1;
    }

//    QtConcurrent::run([=](){
//        GstBus *bus;
//        GstMessage *msg;
//        // 获取管道的总线
//        bus = gst_element_get_bus(pipeline);
//        // 等待消息
//        msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GstMessageType(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

//        // 处理消息
//        if (msg != NULL) {
//            GError *err = NULL;
//            gchar *debug_info = NULL;

//            switch (GST_MESSAGE_TYPE(msg)) {
//                case GST_MESSAGE_ERROR:
//                    gst_message_parse_error(msg, &err, &debug_info);
//                    g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
//                    g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
//                    g_clear_error(&err);
//                    g_free(debug_info);
//                    break;
//                case GST_MESSAGE_EOS:
//                    g_print("End-Of-Stream reached.\n");
//                    break;
//                default:
//                    // 不处理其他消息
//                    break;
//            }
//            gst_message_unref(msg);
//        }
//        gst_object_unref(bus);
//    });

    auto res = a.exec();

    // 释放资源
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);

    return res;
}

三、QT界面中除了显示gstreamer的画面,还能添加控件

原理:

        大家可以自行测试,比如在一个QT的UI文件中,增加了QWidget和各种其他控件,通过之前的绑定overlay的方式,都会发现,其他所有的控件都被显示控件给占满了,原因就是因为获取的是窗口ID,相当于会铺满整个窗口,包括覆盖里面的控件。

结果:会发现所有控件都没有了。

解决方案:

        经过很多次测试,忽然灵机一闪,发现,既然获得的必须是窗口ID,一定要铺满整个窗口的话,那为什么我不直接将显示窗口作为子窗口嵌入到UI界面中呢?

        方案其实很多:

                1. 获取每一帧gstreamer的画面图像,手动绘制到指定界面(太复杂)

                2. 使用setSurface 等方法,手动绘制(太复杂)

                3. 使用Qst,很多时候用不了,难安装(不建议)

                4. 。。。

                5. 我的方案一,直接将显示的窗口当做子窗口嵌入UI界面(简单)

                6. 我的方案二,封装一个QWidget类,里面还有一个播放视频的QWidget,使用子QWidget的ID来绑定显示。

1. 方案一:有UI界面文件的时候:

        我的解决方法就是:使用 QWidget::createWindowContainer 函数,将显示窗口作为子窗口,嵌入到其他有控件的窗口即可。(当然,需要自行调节显示窗口的大小和动态缩放,时间有限,本案例没有写,以后有时间补上)

        参考文章:Qt嵌入外部程序界面初探_qt findmainwindow-CSDN博客

结果:可以发现,遮挡了显示窗口后面的控件,但是在窗口外面的控件正常显示和使用。

        经过不断测试,发现显示窗口上面是无法显示控件的,但是旁边可以,于是可以想到,只要控制好显示窗口的大小和位置,就可以实现很好的显示界面和控件的配合。

以下是简单的demo,时间有限,以后再封装:

  • 先自定义UI,这里为了测试,我设计了4个按钮放在四周:

  • 先放没有改动的时候的测试代码:
#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <QWindow>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget window;
    WId window_handle;

    gst_init(&argc, &argv);

    // 创建管道 pipeline
    // GstElement *pipeline = gst_parse_launch("filesrc name=mysrc ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw, width=640,height=480 ! ximagesink name=vsink", NULL);
    // 设置管道中的属性(创建管道的时候,使用第一条才有效)
    // GstElement *mysrc = gst_bin_get_by_name (GST_BIN (pipeline), "mysrc");
    // g_object_set (mysrc, "location", "/home/enpht/Videos/1081.mp4", NULL);
    // g_object_unref (mysrc);

    // 测试视频:
    GstElement *pipeline = gst_parse_launch("videotestsrc ! ximagesink name=vsink",NULL);

    // 创建界面
    window.show();
    window_handle = window.winId();

    // 链接到QT:
    GstElement *vsink = gst_bin_get_by_name (GST_BIN (pipeline), "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);

    // Start playing
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // Run the QT application loop
    int ret = app.exec();

    // Clean up
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return ret;
}

  • 改动以后的测试代码:

改动1:先创建一个CGstreamPlayWidget UI类,自动生成头文件、cpp文件和UI文件

改动2:导入头文件,然后显示出来

改动3:将之前的Widget设置为不要显示

改动4:创建一个可以嵌入窗口的容器,然后把这个容器加入到UI界面中显示出来。

#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <QWindow>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

#include "cGstreamPlayWidget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QWidget window;
    WId window_handle;
    
    // 此处改动:增加UI界面的显示
    CGstreamPlayWidget gstPlayWidget;
    gstPlayWidget.show();

    gst_init(&argc, &argv);

    // 创建管道 pipeline
    // GstElement *pipeline = gst_parse_launch("filesrc name=mysrc ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw, width=640,height=480 ! ximagesink name=vsink", NULL);
    // 设置管道中的属性(创建管道的时候,使用第一条才有效)
    // GstElement *mysrc = gst_bin_get_by_name (GST_BIN (pipeline), "mysrc");
    // g_object_set (mysrc, "location", "/home/enpht/Videos/1081.mp4", NULL);
    // g_object_unref (mysrc);

    // 测试视频:
    GstElement *pipeline = gst_parse_launch("videotestsrc ! ximagesink name=vsink",NULL);

    // 创建界面
    // 此处改动:先不要显示
    // window.show();
    window_handle = window.winId();

    // 链接到QT:
    GstElement *vsink = gst_bin_get_by_name (GST_BIN (pipeline), "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);

    // 此处改动:获取到窗口的ID,然后创建独立的窗口容器,嵌入到UI界面中**************
    QWindow* m_window;
    QWidget *m_widget;
    m_window= QWindow::fromWinId((WId)(window_handle));
    m_widget = QWidget::createWindowContainer(m_window, &gstPlayWidget);
    m_widget->resize(640,480);
    m_widget->show();
    //**********************************************************************

    // Start playing
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // Run the QT application loop
    int ret = app.exec();

    // Clean up
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return ret;
}
  • 实现效果:发现只覆盖了一个按钮:

  • 这里大家可以自行测试,不影响这三个按钮的点击事件。

总结:

1.  关键是以下这段话:

    // 此处改动:获取到窗口的ID,然后创建独立的窗口容器,嵌入到UI界面中**************
    QWindow* m_window;
    QWidget *m_widget;
    m_window= QWindow::fromWinId((WId)(window_handle));
    m_widget = QWidget::createWindowContainer(m_window, &gstPlayWidget);
    m_widget->resize(640,480);
    m_widget->show();
    //**********************************************************************

        通过QWindow::fromWinId 获取到创建好的创建的窗口ID,然后使用QWidget::createWindowContainer 函数,嵌入到UI界面中,这个函数有三个参数,第一个参数表示刚刚获取的窗口ID(必须经过fromWinId 转换为QWindow才行),第二个参数表示想嵌入到的界面指针,第三个参数这里没有填,自行百度,建议不要填。

2. 成功的关键几个点,大家理解以后,方便以后自行封装:

  • 要创建(new也行)一个单独的Widget,就像我这里的QWidget window; ,不要以为没他可以,其实他的作用是将gstreamer绑定到自己身上

3. 大家自行实验几次以后,就可以发现一些更加高级的用法,比如我自己封装了一个简单的界面用于显示gstreamer的管道画面,通过函数来指定嵌入到哪里去。

2.方案二:没有UI界面文件的时候:

(其实也能使用方案一,然后自己手动加入各种控件,感觉我感觉有点繁琐,后来发现其他方案)

  后来发现,干脆直接封装一个自定义的继承自QWidget的类,这个类中,还有一个QWidget,使用内部的QWidget来绑定即可,就无需那么繁琐。

  封装的额外功能:

  • 提前放置了3个布局,1个垂直布局,2个水平布局,以及3个函数来往布局里面加控件
  • 提前留出了信号,方便以后使用QT关联
  • 预留出gstremer的信号回调函数

自定义封装的类头文件:cGstreamPlayWidget.h

#ifndef CGSTREAMPLAYWIDGET_H
#define CGSTREAMPLAYWIDGET_H

#include <QMainWindow>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSlider>
#include <QTimer>
#include <gst/gst.h>

class CGstreamPlayWidget : public QWidget
{
    Q_OBJECT

public:
    CGstreamPlayWidget(QWidget *parent = nullptr);
    ~CGstreamPlayWidget();

    WId  getVideoWId() const ;               // 获得播放的WID
    void setGstPipline(GstElement* pipline); // 设置管道:将外部管道指针传递进来
    void addWidget_h1Layout(QWidget *w);     // 增加界面到横向布局1里面
    void addWidget_h2Layout(QWidget *w);     // 增加界面到横向布局2里面
    void addWidget_vLayout(QWidget *w);      // 增加界面到纵向布局里面

    // 处理bus信息: 外部使用案例GstBus *bus = gst_element_get_bus(pipeline); gst_bus_add_watch(bus, &CGstreamPlayWidget::postGstMessage, window); window是创建的此类对象
    static gboolean postGstMessage(GstBus * bus, GstMessage * message, gpointer user_data);

private:
    GstElement *pipeline = nullptr;         // 管道
    QWidget *videoWindow = nullptr;         // 播放窗口
    QHBoxLayout *h1Layout = nullptr;         // 横向布局
    QHBoxLayout *h2Layout = nullptr;         // 横向布局
    QVBoxLayout *vLayout = nullptr;         // 纵向布局

signals:
  void sigAlbum(const QString &album);      // 发送视频专辑名字信息
  void sigState(GstState st);               // 发送播放状态信息
  void sigEos();                            // 发送文件结束信号

};

#endif // CGSTREAMPLAYWIDGET_H

自定义类的cpp文件:

#include "cGstreamPlayWidget.h"

CGstreamPlayWidget::CGstreamPlayWidget(QWidget *parent)
    : QWidget(parent)
{
    videoWindow = new QWidget();
    h1Layout = new QHBoxLayout();
    h2Layout = new QHBoxLayout();
    vLayout = new QVBoxLayout();

    vLayout->addLayout(h1Layout);
    vLayout->addWidget(videoWindow);
    vLayout->addLayout(h2Layout);
    this->setLayout(vLayout);
}

CGstreamPlayWidget::~CGstreamPlayWidget()
{
    if(pipeline){
        /* 停止管道 */
        gst_element_set_state(pipeline, GST_STATE_NULL);

        /* 释放资源 */
        gst_object_unref(pipeline);
    }

}

WId CGstreamPlayWidget::getVideoWId() const
{
    return videoWindow->winId();
}

void CGstreamPlayWidget::addWidget_h1Layout(QWidget *w)
{
    h1Layout->addWidget(w);
}

void CGstreamPlayWidget::addWidget_h2Layout(QWidget *w)
{
    h2Layout->addWidget(w);
}

void CGstreamPlayWidget::addWidget_vLayout(QWidget *w)
{
    vLayout->addWidget(w);
}

gboolean CGstreamPlayWidget::postGstMessage(GstBus *bus, GstMessage *message, gpointer user_data)
{
    CGstreamPlayWidget *pw = NULL;
    if (user_data) {
        pw = reinterpret_cast<CGstreamPlayWidget*>(user_data);
    }
    switch (GST_MESSAGE_TYPE(message)) {
        case GST_MESSAGE_STATE_CHANGED: {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (message, &old_state, &new_state, &pending_state);
            pw->sigState(new_state);
            break;
        }
        case GST_MESSAGE_TAG: {
            GstTagList *tags = NULL;
            gst_message_parse_tag(message, &tags);
            gchar *album= NULL;
            if (gst_tag_list_get_string(tags, GST_TAG_ALBUM, &album)) {
                pw->sigAlbum(album);
                g_free(album);
            }
            gst_tag_list_unref(tags);
            break;
        }
        case GST_MESSAGE_EOS: {
            pw->sigEos();
            break;
        }
        default:
            break;
    }
    return TRUE;
}

使用测试1:简单测试

#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <QWindow>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

#include "cGstreamPlayWidget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    WId window_handle;

    // 创建自定义界面
    CGstreamPlayWidget gstPlayWidget;
    gstPlayWidget.resize(800,600);
    gstPlayWidget.show();

    gst_init(&argc, &argv);

    window_handle = gstPlayWidget.getVideoWId();

// 测试增加控件
#if 1
    // 增加控件:
    #include <QPushButton>
    QPushButton pbt1("test1");
    QPushButton pbt2("test2");
    QPushButton pbt3("test3");
    gstPlayWidget.addWidget_h1Layout(&pbt1);
    gstPlayWidget.addWidget_h1Layout(&pbt3);
    gstPlayWidget.addWidget_vLayout(&pbt2);

#endif

// 测试创建管道
#if 1
    // 创建管道方法1:
    // 测试视频:
    GstElement *pipeline = gst_parse_launch("videotestsrc ! glimagesink name=vsink",NULL);  //ximagesink  xvimagesink   glimagesink
    // 链接到QT:
    GstElement *vsink = gst_bin_get_by_name (GST_BIN (pipeline), "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);

#elif
    // 创建管道方法2:
    // 测试视频:播放本地文件
    GstElement *pipeline = gst_parse_launch ("playbin uri=file:home/enpht/Videos/1080.mp4", NULL);
    // 链接到QT:
    GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);
    g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);

    // 创建管道方法3:
    // 创建管道 pipeline
    // GstElement *pipeline = gst_parse_launch("filesrc name=mysrc ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw, width=640,height=480 ! ximagesink name=vsink", NULL);
    // 设置管道中的属性(创建管道的时候,使用第一条才有效)
    // GstElement *mysrc = gst_bin_get_by_name (GST_BIN (pipeline), "mysrc");
    // g_object_set (mysrc, "location", "/home/enpht/Videos/1081.mp4", NULL);
    // g_object_unref (mysrc);
    // 链接到QT:
    // GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
    // gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);
    // g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);

#endif

// 测试呼出事件:
#if 0
    GstBus *bus = gst_element_get_bus(pipeline);
    gst_bus_add_watch(bus, &CGstreamPlayWidget::postGstMessage, &gstPlayWidget);
    gst_object_unref(bus);

#endif

    // 开始播放
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // QT的事件循环
    int ret = app.exec();

    // 释放内存
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return ret;
}

效果:

使用测试2:使用提升的方式

  • 新建一个QMainWidget 的UI类,取名为MainWindowTest
  • 编写UI: 加入一个空Widget,然后右键提升,然后指定刚刚的自定义类,再加入几个控件
  • 在MainWindowTest 类中,加入一个函数QWidget * getPlayWidget();  获取界面UI的界面指针
QWidget *MainWindowTest::getPlayWidget()
{
    return ui->widget;
}
  • 重写编写测试代码:

        改动的地方:

        1.导入新头文件,使用那个函数获取指针:

        CGstreamPlayWidget *gstPlayWidget = (CGstreamPlayWidget*)(w.getPlayWidget());

        2.将gstPlayWidget的show去掉,将gstPlayWidget. 改为 gstPlayWidget->

#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <QWindow>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

#include "cGstreamPlayWidget.h"
#include "mainWindowTest.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    WId window_handle;


    MainWindowTest w;
    w.show();

    // 创建自定义界面
    CGstreamPlayWidget *gstPlayWidget = (CGstreamPlayWidget*)(w.getPlayWidget());
    gstPlayWidget->resize(800,600);
    gstPlayWidget->show();

    gst_init(&argc, &argv);

    window_handle = gstPlayWidget->getVideoWId();

// 测试增加控件
#if 1
    // 增加控件:
    #include <QPushButton>
    QPushButton pbt1("test1");
    QPushButton pbt2("test2");
    QPushButton pbt3("test3");
    gstPlayWidget->addWidget_h1Layout(&pbt1);
    gstPlayWidget->addWidget_h1Layout(&pbt3);
    gstPlayWidget->addWidget_vLayout(&pbt2);

#endif

// 测试创建管道
#if 1
    // 创建管道方法1:
    // 测试视频:
    GstElement *pipeline = gst_parse_launch("videotestsrc ! glimagesink name=vsink",NULL);  //ximagesink  xvimagesink   glimagesink
    // 链接到QT:
    GstElement *vsink = gst_bin_get_by_name (GST_BIN (pipeline), "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);

#elif
    // 创建管道方法2:
    // 测试视频:播放本地文件
    GstElement *pipeline = gst_parse_launch ("playbin uri=file:home/enpht/Videos/1080.mp4", NULL);
    // 链接到QT:
    GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);
    g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);

    // 创建管道方法3:
    // 创建管道 pipeline
    // GstElement *pipeline = gst_parse_launch("filesrc name=mysrc ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw, width=640,height=480 ! ximagesink name=vsink", NULL);
    // 设置管道中的属性(创建管道的时候,使用第一条才有效)
    // GstElement *mysrc = gst_bin_get_by_name (GST_BIN (pipeline), "mysrc");
    // g_object_set (mysrc, "location", "/home/enpht/Videos/1081.mp4", NULL);
    // g_object_unref (mysrc);
    // 链接到QT:
    // GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
    // gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), window_handle);
    // g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);

#endif

// 测试呼出事件:
#if 0
    GstBus *bus = gst_element_get_bus(pipeline);
    gst_bus_add_watch(bus, &CGstreamPlayWidget::postGstMessage, &gstPlayWidget);
    gst_object_unref(bus);

#endif

    // 开始播放
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // QT的事件循环
    int ret = app.exec();

    // 释放内存
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT(pipeline));

    return ret;
}

效果:会神奇的发现,控件竟然可以在画面上显示了!!!

方案对比:

        毫无疑问,这两种方案,都比自己写绘制的代码要方便的多,但是这两种方案各有优劣,当然优点肯定是第二个更多一些。各自有各自的适应环境,比如,如果已经确定了UI界面,那么干脆就使用第一种,将整个播放界面嵌入进更大的界面去。其他情况,都更加适合第二种,因为可以使用提升的方式,很好的去布局,内部有加入控件的接口,也方便接入,同时我也预留了接口。

最后:因为写这篇文章也耗费了一些精力,希望可以点赞收藏

项目源码:

(QT播放gstreamer管道命令的示例资源-CSDN文库)

源码使用注意事项:

1. 请百度,或者看我前面的文章,自行安装gstreamer和QT环境

2. 各个虚拟机和板子的环境配置不一样,需要自己在pro文件中微调:

    例如下面的/usr/lib/x86_64-linux-gnu/glib-2.0/include   这个需要调

CONFIG += link_pkgconfig
PKGCONFIG += gstreamer-1.0 gstreamer-plugins-base-1.0 gtk+-3.0

LIBS += -lX11
LIBS    +=-lglib-2.0
LIBS    +=-lgobject-2.0
LIBS    +=-lgstreamer-1.0          # <gst/gst.h>
LIBS    +=-lgstvideo-1.0             # <gst/video/videooverlay.h>
LIBS    +=-L/usr/lib/x86_64-linux-gnu/gstreamer-1.0
LIBS    +=-lgstrtspserver-1.0
LIBS    +=-lgstautodetect
LIBS    +=-lgstaudio-1.0
LIBS    +=-lgstapp-1.0

INCLUDEPATH += \
            /usr/include/glib-2.0 \
            /usr/lib/x86_64-linux-gnu/glib-2.0/include \
            /usr/include/gstreamer-1.0 \
            /usr/lib/x86_64-linux-gnu/gstreamer-1.0/include/

    以下是我自己用的环境配置:

虚拟机Linux:**
 LIBS    +=-lglib-2.0
 LIBS    +=-lgobject-2.0
 LIBS    +=-lgstreamer-1.0          # <gst/gst.h>
 LIBS    +=-lgstvideo-1.0             # <gst/video/videooverlay.h>
 LIBS    +=-L/usr/lib/x86_64-linux-gnu/gstreamer-1.0
 LIBS    +=-lgstautodetect -lgstapp-1.0
 INCLUDEPATH += 
             /usr/include/glib-2.0 
             /usr/lib/x86_64-linux-gnu/glib-2.0/include 
             /usr/include/gstreamer-1.0 
             /usr/lib/x86_64-linux-gnu/gstreamer-1.0/include/


也可以使用:
CONFIG += link_pkgconfig
PKGCONFIG += gstreamer-1.0 gstreamer-plugins-base-1.0


arm的Linux:
 LIBS    +=-lglib-2.0
 LIBS    +=-lgobject-2.0
 LIBS    +=-lgstreamer-1.0          # <gst/gst.h>
 LIBS    +=-lgstvideo-1.0             # <gst/video/videooverlay.h>
 LIBS    +=-L/usr/lib/aarch64-linux-gnu/gstreamer-1.0
 LIBS    +=-lgstautodetect -lgstapp-1.0
 INCLUDEPATH += 
             /usr/include/glib-2.0 
             /usr/lib/aarch64-linux-gnu/glib-2.0/include 
             /usr/include/gstreamer-1.0 
             /usr/lib/aarch64-linux-gnu/gstreamer-1.0/include/


window:
INCLUDEPATH += $$PWD/gstreamer/include/gstreamer-1.0/gst
INCLUDEPATH += $$PWD/gstreamer/include
INCLUDEPATH += $$PWD/gstreamer/include/gstreamer-1.0
INCLUDEPATH += $$PWD/gstreamer/include/glib-2.0
INCLUDEPATH += $$PWD/gstreamer/lib/glib-2.0/include
LIBS += -L$$PWD/gstreamer/lib/ -lgstreamer-1.0 -lgstvideo-1.0 -lgobject-2.0 -lglib-2.0


RK3588:
#INCLUDEPATH += $$PWD/gstreamer/include/gstreamer-1.0/gst
INCLUDEPATH += /opt/enpht/rk3588/sysroot/usr/include/gstreamer-1.0/gst

#INCLUDEPATH += $$PWD/gstreamer/include
INCLUDEPATH += /opt/enpht/rk3588/sysroot/usr/include

#INCLUDEPATH += $$PWD/gstreamer/include/gstreamer-1.0
INCLUDEPATH += /opt/enpht/rk3588/sysroot/usr/include/gstreamer-1.0

#INCLUDEPATH += $$PWD/gstreamer/include/glib-2.0
INCLUDEPATH += /opt/enpht/rk3588/sysroot/usr/include/glib-2.0

#INCLUDEPATH += $$PWD/gstreamer/lib/glib-2.0/include
INCLUDEPATH += /opt/enpht/rk3588/sysroot/usr/lib/aarch64-linux-gnu/glib-2.0/include

#LIBS += -L$$PWD/gstreamer/lib/ -lgstreamer-1.0 -lgstvideo-1.0 -lgobject-2.0 -lglib-2.0
LIBS += -L/opt/enpht/rk3588/sysroot/usr/lib/aarch64-linux-gnu/ -lgstreamer-1.0 -lgstvideo-1.0 -lgobject-2.0 -lglib-2.0 -lgstbase-1.0
#LIBS += -L/usr/lib/x86_64-linux-gnu/ -lgstreamer-1.0 -lgstvideo-1.0 -lgobject-2.0 -lglib-2.0

#QT_CONFIG -= no-pkg-config
#CONFIG += link_pkgconfig debug
#PKGCONFIG = \
#    gstreamer-1.0 \
#    gstreamer-video-1.0

3. 最后的opencv 那个库,大家没有安装的话,可以删掉,可以不用加,删了不影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1418569.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

echarts:获取省、市、区/县、镇的地图数据

目录 第一章 前言 第二章 获取地图的数据&#xff08;GeoJSON格式&#xff09; 2.1 获取省、市、区/县地图数据 2.2 获取乡/镇/街道地图数据 第一章 前言 需求&#xff1a;接到要做大屏的需求&#xff0c;其中需要用echarts绘画一个地图&#xff0c;但是需要的地图是区/县…

AI语音机器人,智能语音交互

随着人工智能技术的不断发展&#xff0c;AI语音机器人软件在电销行业中得到了广泛应用。这些软件可以通过自动拨打功能&#xff0c;提高销售效率&#xff0c;降低成本&#xff0c;提升客户体验。AI语音机器人软件的主要功能是自动拨打电话。它可以根据预设的规则和算法&#xf…

如何使用Everything随时随地远程访问本地电脑搜索文件

文章目录 前言1.软件安装完成后&#xff0c;打开Everything2.登录cpolar官网 设置空白数据隧道3.将空白数据隧道与本地Everything软件结合起来总结 前言 要搭建一个在线资料库&#xff0c;我们需要两个软件的支持&#xff0c;分别是cpolar&#xff08;用于搭建内网穿透数据隧道…

【竞技宝】DOTA2:LGD正式官宣emo离队 setsu加盟担任二号位

北京时间2024年1月29日,随着新年的到来,DOTA2赛事已经进入了新的篇章。本月虽然没有迎来大型赛事,但各种赛事的预选赛却打了不少。国内战队方面,LGD在今年换人之后表现较差,此前传言LGD将对阵容进行调整,就在昨天LGD终于官宣了新年的首次阵容变动。 昨日,LGD在官方微博发布公告…

算法设计与分析实验:滑动窗口与二分查找

目录 一、寻找两个正序数组的中位数 1.1 具体思路 1.2 流程展示 1.3 代码实现 1.4 代码复杂度分析 1.5 运行结果 二、X的平方根 2.1 具体思路 2.2 流程展示 2.3 代码实现 2.4 代码复杂度分析 2.5 运行结果 三、两数之和 II-输入有序数组 3.1 采用二分查找的思想 …

【C深度解剖】const关键字

简介&#xff1a;本系列博客为C深度解剖系列内容&#xff0c;以某个点为中心进行相关详细拓展 适宜人群&#xff1a;已大体了解C语法同学 作者留言&#xff1a;本博客相关内容如需转载请注明出处&#xff0c;本人学疏才浅&#xff0c;难免存在些许错误&#xff0c;望留言指正 作…

4核16G10M幻兽帕鲁服务器只需26元/月?!阿里云挑战全网最低价

1月29日&#xff0c;阿里云又发大招&#xff01;将原先4核16G3M的套餐直接升级至10M&#xff0c;价格从32元直降至26元&#xff01; 目前已是全网幻兽帕鲁4-8人游玩配置的服务器性价比极高的套餐&#xff01; 》》阿里云4核16G10M 26元/月 整理至1月29日&#xff0c;各家幻兽…

Ubuntu本地部署Nextcloud并结合内网穿透实现远程访问搭建个人云盘

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 摘要1. 环境搭建2. 测试局域网访问3. 内网穿透3.1 ubuntu本地安装cpolar3.2 创建隧道3.3 测试公网访…

Mac+Android Studio配置 Flutter环境

Fluttrer中文下载官网 Flutter下载官网 1、环境变量 .zshrc #Flutter export PUB_HOSTED_URL"https://pub.flutter-io.cn" export FLUTTER_STORAGE_BASE_URL"https://storage.flutter-io.cn" export FLUTTER_HOME/Users/leon/Flutter/flutter_3_10_4/f…

强化学习-google football 实验记录

google football 实验记录 1. gru模型和dense模型对比实验 实验场景&#xff1a;5v5(控制蓝方一名激活球员)&#xff0c;跳4帧&#xff0c;即每个动作执行4次 实验点&#xff1a; 修复dense奖励后智能体训练效果能否符合预期 实验目的&#xff1a; 对比gru 长度为16 和 dens…

【前端web入门第二天】02 表单-input标签-单选框-多选框

表单 文章目录: 1.input标签基本使用 1.1 input标签占位文本1.2 单选框 radio 1.3 多选框 checkbox 作用:收集用户信息。 使用场景: 登录页面注册页面搜索区域 1.input标签基本使用 input标签type属性值不同&#xff0c;则功能不同。 <input type"..."&g…

如何搭建开源笔记Joplin服务并实现远程访问本地数据

文章目录 1. 安装Docker2. 自建Joplin服务器3. 搭建Joplin Sever4. 安装cpolar内网穿透5. 创建远程连接的固定公网地址 Joplin 是一个开源的笔记工具&#xff0c;拥有 Windows/macOS/Linux/iOS/Android/Terminal 版本的客户端。多端同步功能是笔记工具最重要的功能&#xff0c;…

【深度学习:目标检测】深度学习中目标检测模型、用例和示例

【深度学习&#xff1a;目标检测】深度学习中目标检测模型、用例和示例 什么是物体检测&#xff1f;物体检测与图像分类物体检测与图像分割 计算机视觉中的目标检测物体检测的优点物体检测的缺点深度学习和目标检测人员检测 物体检测如何工作&#xff1f;一阶段与两阶段深度学习…

从0开始搭建若依微服务项目 RuoYi-Cloud(保姆式教程 一)

掌握陌生项目解读技巧 掌握若依(RuoYi-Cloud)框架 掌握SpringCloud Alibaba体系项目开发套路&#xff0c;结合我之前所有企业项目来学习就知道有多么简单。 一、框架介绍 1. 简介 一直想做一款后台管理系统&#xff0c;看了很多优秀的开源项目但是发现没有合适的。于是利用空…

老司机用脚本批量巧删恶意文件

作者&#xff1a;田逸&#xff08;formyz&#xff09; 一个NFS服务器&#xff0c;为多个Web项目所共享。这些目录包括PHP程序、图片、HTML页面和用户上传的文档和附件等。因为某些Web框架古老&#xff0c;存在诸如不对上传文件做严格的安全性检查&#xff0c;虽然此NFS服务器位…

OceanMind海睿思入选《2023大数据产业年度创新技术突破奖》,并蝉联多项图谱

近日&#xff0c;由数据猿和上海大数据联盟主办&#xff0c;上海市经济和信息化委员会、上海市科学技术委员会指导的“第六届金猿季&魔方论坛——大数据产业发展论坛”在上海成功举行&#xff0c;吸引了数百位业界精英的参与。中新赛克海睿思作为国内数字化转型优秀厂商代表…

虚拟机安装Centos8.5

记得看目录哦&#xff01; 附件1. 新建虚拟机2. 安装Centos8.5 附件 安装包自行下载 https://mirrors.aliyun.com/centos/8/isos/x86_64/ 1. 新建虚拟机 2. 安装Centos8.5 启动虚拟机–选择第一个install Centos8.5 记得接收许可证

25考研北大软微该怎么做?

25考研想准备北大软微&#xff0c;那肯定要认真准备了 考软微需要多少实力 现在的软微已经不是以前的软微了&#xff0c;基本上所有考计算机的同学都知道&#xff0c;已经没有什么信息优势了&#xff0c;只有实打实的有实力的选手才建议报考。 因为软微的专业课也是11408&am…

在Windows上安装与配置Apache服务并结合内网穿透工具实现公网远程访问本地内网服务

文章目录 前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web ui管理界面3.2 创建公网地址 4. 固定公网地址 前言 Apache作为全球使用较高的Web服务器…

Web开发8:前后端分离开发

在现代的 Web 开发中&#xff0c;前后端分离开发已经成为了一种常见的架构模式。它的优势在于前端和后端可以独立开发&#xff0c;互不干扰&#xff0c;同时也提供了更好的可扩展性和灵活性。本篇博客将介绍前后端分离开发的概念、优势以及如何实现。 什么是前后端分离开发&am…