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

 一、前言

      上一篇文章写到了,如何快速使用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 那个库,大家没有安装的话,可以删掉,可以不用加,删了不影响。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码训练营Day.48 | 198. 打家劫舍、213. 打家劫舍II、337. 打家劫舍III

198. 打家劫舍 1. LeetCode链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 2. 题目描述 3. 解法 可以看作一个01背包问题。背包容量为所有房子中存储的金钱总数。 1. dp数组含义&#xff1a;dp[i][j]表示前i个房子在背包容量为j的情况下可以…

Arrays.asList()方法调用add()或remove()抛出java.lang.UnsupportedOperationException问题

在使用Arrays.asList方法将以,分割的字符串转为list集合时&#xff0c;调用add和remove等方法时会抛出java.lang.UnsupportedOperationException。以下为原因和解决方法。 原因&#xff1a; Arrays.asList()方法返回了一个Arrays类的一个继承了AbstractList的ArrayList内部类…

Python面向对象-类专题

在Python中&#xff0c;if __name__ __main__: 这一句是一个常见的模式&#xff0c;用于判断当前的模块是被直接运行还是被导入到其他模块中。 当Python文件被直接运行时&#xff0c;其内置的__name__变量被设置为__main__。但如果这个文件被其他文件导入&#xff0c;__name__…

面向云服务的GaussDB全密态数据库

前言 全密态数据库&#xff0c;顾名思义与大家所理解的流数据库、图数据库一样&#xff0c;就是专门处理密文数据的数据库系统。数据以加密形态存储在数据库服务器中&#xff0c;数据库支持对密文数据的检索与计算&#xff0c;而与查询任务相关的词法解析、语法解析、执行计划生…

海外云手机为什么吸引用户?

近年来&#xff0c;随着全球化的飞速发展&#xff0c;海外云手机逐渐成为各行各业关注的焦点。那么&#xff0c;究竟是什么让海外云手机如此吸引用户呢&#xff1f;本文将深入探讨海外云手机的三大吸引力&#xff0c;揭示海外云手机的优势所在。 1. 高效的社交媒体运营 海外云…

盒子模型的内容总结

知识引入 1.认识盒子模型 在浏览网站时我们会发现内容都是按照区域划分的。这使得网页很工整、美观。在页面中&#xff0c;每一块区域分别承载不同的内容&#xff0c;使得网页的内容虽然零散&#xff0c;但是在版式排列上依然清晰有条理。如图1 图1 *承载内容的区域称为盒子…

Windows系统安装OpenSSH+VS Code结合内网穿透实现远程开发

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

【Lazy ORM 整合druid 实现mysql监控】

Lazy ORM 整合druid 实现mysql监控 JDK 17 Lazy ORM框架地址 up、up欢迎start、issues 当前项目案例地址 框架版本描述spring-boot3.0.7springboot框架wu-framework-web1.2.2-JDK17-SNAPSHOTweb容器Lazy -ORM1.2.2-JDK17-SNAPSHOTORMmysql-connector-j8.0.33mysql驱动druid-…

【人工智能课程】计算机科学博士作业二

使用TensorFlow1.x版本来实现手势识别任务中&#xff0c;并用图像增强的方式改进&#xff0c;基准训练准确率0.92&#xff0c;测试准确率0.77&#xff0c;改进后&#xff0c;训练准确率0.97&#xff0c;测试准确率0.88。 1 导入包 import math import warnings warnings.filt…

七、内存管理单元(MMU)

前言 在多任务的处理器上&#xff0c;往往运行着许多的用户进程&#xff0c;这些进程之间相互隔离&#xff0c;它们都有自己的虚拟存储空间。要实现这样的虚拟存储空间&#xff0c;需要可以进行地址重分配以及虚拟地址到物理地址的转换。 MMU就是实现这种功能的硬件部件&…

哨兵1号回波数据(L0级)提取与SAR成像(全网首发)

本专栏目录:全球SAR卫星大盘点与回波数据处理专栏目录 本文先展示提取出的回波结果,然后使用RD算法进行成像,展示成像结果,最后附上哨兵1号回波提取的MATLAB代码。 1. 回波提取 回波提取得到二维复矩阵数据,对其求模值后绘图如下(横轴为距离向采样点,纵轴为方位向采样…

如何高效复制加密狗:一篇加密狗复制的常见方法全面指南

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

C++实现通讯录管理系统

目录 1、系统需求 2、创建项目 2.1 创建项目 3、菜单功能 4、退出功能 5、添加联系人 5.1 设计联系人结构体 5.2 设计通讯录结构体 5.3 main函数中创建通讯录 5.4 封装联系人函数 5.5 测试添加联系人功能 6、显示联系人 6.1 封装显示联系人函数 7、删除联系人 7.1…

Spring Security简介

什么是Spring Security Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我 们来简化认证和授权的过程。 官网&#xff1a;Spring Security 对应的maven坐标&#xff1a; <!--security启动器--> <dependency><groupId>or…

Scala入门01

Spark入门 1.入门 spark采用Scala语言开发 Spark是用来计算的 Scala掌握&#xff1a;特性&#xff0c;基本操作&#xff0c;集合操作&#xff0c;函数&#xff0c;模式匹配&#xff0c;trait&#xff0c;样例类&#xff0c;actor等内容。 2.内容讲解 2.1 Scala简介 在http…

Missing or invalid credentials.(Git push报错解决方案)

前言 本文主要讲解git push后报错Missing or invalid credentials的解决方案。这里针对的是windows的。 编程环境&#xff1a;VsCode 问题原因 问题翻译起来就是 凭据缺失或无效。这里我们解决方案是取消vscode里面默认的控制终端git凭据来解决,具体方案如下. 解决方案 1…

金田金业:中国大妈十年炒黄金后有何启发? 黄金交易要有策略

十多年前的2013年中国大妈炒黄金曾威震华尔街&#xff0c;一度成为投资界佳话。当时经过经历2008年全球金融危机五年后随着美国经济持续改善、美国缩减量化宽松规模&#xff0c;在美元强力反弹压制下&#xff0c;国际现货黄金从1700美元下跌到1300美元左右。正是在这个相对低点…

Shell中正则表达式

1.正则表达式介绍 1、正则表达式---通常用于判断语句中&#xff0c;用来检查某一字符串是否满足某一格式 2、正则表达式是由普通字符与元字符组成 3、普通字符包括大小写字母、数字、标点符号及一些其他符号 4、元字符是指在正则表达式中具有特殊意义的专用字符&#xff0c…

C++------高精度减法

题目描述&#xff1a; 分析&#xff1a; 一、A - B分两种情况&#xff1a; 当A>B ----> A - B&#xff1b;当A<B ----> -(B-A); 二、借位 t 的情况&#xff1a; t > 0 : 说明t不需要借位t < 0 : 说明 需要 t10 去补 AC代码如下&#xff1a; #in…

新建react项目,react-router-dom配置路由,引入antd

提示&#xff1a;reactrouter6.4版本&#xff0c;与reactrouter5.0的版本用法有区别&#xff0c;互不兼容需注意 文章目录 前言一、创建项目二、新建文件并引入react-router-dom、antd三、配置路由跳转四、效果五、遇到的问题六、参考文档总结 前言 需求&#xff1a;新建react项…