Bootstrap

Gstreamer系列(8):Gstreamer集成Qt开发视频播放器

1. 前言

        在工程应用中通常使用Gstreamer来作为播放器,同时又需要其他功能,比如:播放,暂停,停止等功能。因此需要一个GUI开发组件,而Qt作为工业控制领域常用的GUI开发软件,所以将Qt与Gstreamer结合开发是十分有必要的。本文将介绍将Gstreamer视频输出到指定窗口,以及实现对视频的播放、暂停、停止和快进等功能。

2. 核心代码

   将Qt创建的视频窗口ID设置给Gstreamer,同时窗口新的ximagesink插件给Gstreamer管道使用,达到最终管道的播放sink窗口为Qt窗口的目的。

// seg window id to gstreamer
GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
// 获取Qt窗口ID
WId xwinid = window->getVideoWId();  
// 给ximagesink设置Qt窗口ID
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), xwinid);
// 设置管道最终sink为上面的ximagesink 
g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL); 

3. 完整代码

完整代码如下,也可以直接下载整个工程https://download.csdn.net/download/qq_27897937/89892328

# pro文件
QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
ONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += link_pkgconfig
PKGCONFIG +=  gstreamer-1.0 gstreamer-app-1.0  gstreamer-video-1.0
SOURCES += \
    main.cpp \
    mainwindow.cpp
HEADERS += \
    mainwindow.h
//main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
    gst_init (&argc, &argv);
    QApplication app(argc, argv);
    app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit ()));

    // prepare the pipeline
    GstElement *pipeline = gst_parse_launch ("playbin uri=file:///home/work/80s.mp4", NULL);

    // prepare the ui
    MainWindow *window = new MainWindow(pipeline);
    window->resize(900, 600);
    window->show();

    // seg window id to gstreamer
    GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
    WId xwinid = window->getVideoWId();
    gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), xwinid);
    g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);

    // connect to interesting signals
    GstBus *bus = gst_element_get_bus(pipeline);
    gst_bus_add_watch(bus, &MainWindow::postGstMessage, window);
    gst_object_unref(bus);

    // run the pipeline
    GstStateChangeReturn sret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
    if (sret == GST_STATE_CHANGE_FAILURE) {
        gst_element_set_state (pipeline, GST_STATE_NULL);
        gst_object_unref (pipeline);
        // Exit application
        QTimer::singleShot(0, QApplication::activeWindow(), SLOT(quit()));
    }

    int ret = app.exec();

    window->hide();
    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref (pipeline);

return ret;
}
//  mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <gst/gst.h>
#include <gst/video/videooverlay.h>

#include <QWidget>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSlider>
#include <QTimer>

class MainWindow : public QWidget
{
    Q_OBJECT
public:
  MainWindow(GstElement *p);

  WId getVideoWId() const ;
  static gboolean postGstMessage(GstBus * bus, GstMessage * message, gpointer user_data);

private slots:
  void onPlayClicked() ;
  void onPauseClicked() ;
  void onStopClicked() ;
  void onAlbumAvaiable(const QString &album);
  void onState(GstState st);
  void refreshSlider();
  void onSeek();
  void onEos();

signals:
  void sigAlbum(const QString &album);
  void sigState(GstState st);
  void sigEos();

private:
  GstElement *pipeline;
  QPushButton *playBt;
  QPushButton *pauseBt;
  QPushButton *stopBt;
  QWidget *videoWindow;
  QSlider *slider;
  QHBoxLayout *buttonLayout;
  QVBoxLayout *playerLayout;
  QTimer *timer;

  GstState state;
  gint64 totalDuration;
};

#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(GstElement *p)
    :pipeline(p)
    ,state(GST_STATE_NULL)
    ,totalDuration(GST_CLOCK_TIME_NONE)
{
    playBt = new QPushButton("Play");
    pauseBt = new QPushButton("Pause");
    stopBt = new QPushButton("Stop");
    videoWindow = new QWidget();
    slider = new QSlider(Qt::Horizontal);
    timer = new QTimer();

    connect(playBt, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
    connect(pauseBt, SIGNAL(clicked()), this, SLOT(onPauseClicked()));
    connect(stopBt, SIGNAL(clicked()), this, SLOT(onStopClicked()));
    connect(slider, SIGNAL(sliderReleased()), this, SLOT(onSeek()));

    buttonLayout = new QHBoxLayout;
    buttonLayout->addWidget(playBt);
    buttonLayout->addWidget(pauseBt);
    buttonLayout->addWidget(stopBt);
    buttonLayout->addWidget(slider);

    playerLayout = new QVBoxLayout;
    playerLayout->addWidget(videoWindow);
    playerLayout->addLayout(buttonLayout);

    this->setLayout(playerLayout);

    connect(timer, SIGNAL(timeout()), this, SLOT(refreshSlider()));
    connect(this, SIGNAL(sigAlbum(QString)), this, SLOT(onAlbumAvaiable(QString)));
    connect(this, SIGNAL(sigState(GstState)), this, SLOT(onState(GstState)));
    connect(this, SIGNAL(sigEos()), this, SLOT(onEos()));
}

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

void MainWindow::onPlayClicked() {
    GstState st = GST_STATE_NULL;
    gst_element_get_state (pipeline, &st, NULL, GST_CLOCK_TIME_NONE);
    if (st < GST_STATE_PAUSED) {
        // Pipeline stopped, we need set overlay again
        GstElement *vsink = gst_element_factory_make ("ximagesink", "vsink");
        g_object_set(GST_OBJECT(pipeline), "video-sink", vsink, NULL);
        WId xwinid = getVideoWId();
        gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (vsink), xwinid);
    }
    gst_element_set_state (pipeline, GST_STATE_PLAYING);
}

void MainWindow::onPauseClicked() {
    gst_element_set_state (pipeline, GST_STATE_PAUSED);
}

void MainWindow::onStopClicked() {
    gst_element_set_state (pipeline, GST_STATE_NULL);
}

void MainWindow::onAlbumAvaiable(const QString &album) {
    setWindowTitle(album);
}

void MainWindow::onState(GstState st) {
    if (state != st) {
        state = st;
        if (state == GST_STATE_PLAYING){
            timer->start(1000);
        }
        if (state < GST_STATE_PAUSED){
            timer->stop();
        }
    }
}

void MainWindow::refreshSlider() {
    gint64 current = GST_CLOCK_TIME_NONE;
    if (state == GST_STATE_PLAYING) {
        if (!GST_CLOCK_TIME_IS_VALID(totalDuration)) {
            if (gst_element_query_duration (pipeline, GST_FORMAT_TIME, &totalDuration)) {
                slider->setRange(0, totalDuration/GST_SECOND);
            }
        }
        if (gst_element_query_position (pipeline, GST_FORMAT_TIME, &current)) {
            g_print("%ld / %ld\n", current/GST_SECOND, totalDuration/GST_SECOND);
            slider->setValue(current/GST_SECOND);
        }
    }
}

void MainWindow::onSeek() {
    gint64 pos = slider->sliderPosition();
    g_print("seek: %ld\n", pos);
    gst_element_seek_simple (pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH ,
                  pos * GST_SECOND);
}

void MainWindow::onEos() {
    gst_element_set_state (pipeline, GST_STATE_NULL);
}

gboolean MainWindow::postGstMessage(GstBus * bus, GstMessage * message, gpointer user_data) {
    MainWindow *pw = NULL;
    if (user_data) {
        pw = reinterpret_cast<MainWindow*>(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;
}

;