简介
-
Qt 是跨平台C++图形用户界面应用程序开发框架。
-
GStreamer是一个用于构造媒体处理组件图的库。它支持的应用程序范围从简单的Ogg/Vorbis 播放, audio/video 流化到复杂的 audio(混音) 和 video (非线性编辑) 处理。
通过Qt + Gstreamer实现一个简单的本地文件播放为例。
本示例通过设置一个播放列表,随机播放视频列表里的文件,当一个文件播放结束后,继续随机播放下一个文件。
准备
1、安装QT
2、安装gstreamer开发库
sudo apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools libgstreamer-plugins-base1.0-dev
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVector>
#include "gstplayer.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void init();
void play();
private slots:
void slt_onEos();
void slt_onError(QString errMsg);
private:
void initPlayList();
private:
Ui::MainWindow *ui;
GstPlayer *m_player;
QVector<QString> m_videos;
QString m_curPlay;
qint32 m_curIdx;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "gstplayer.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QWidget *wid = centralWidget();
GstPlayer::gstInit();
m_player = new GstPlayer(wid->winId());
connect(m_player, SIGNAL(sig_eos()), this, SLOT(slt_onEos()));
connect(m_player, SIGNAL(sig_error(QString)), this, SLOT(slt_onError(QString)));
}
MainWindow::~MainWindow()
{
delete m_player;
delete ui;
}
void MainWindow::init()
{
initPlayList();
}
void MainWindow::slt_onEos()
{
play();
}
void MainWindow::slt_onError(QString errMsg)
{
qDebug() << m_curPlay << errMsg;
m_videos.erase(m_videos.begin() + m_curIdx);
play();
}
void MainWindow::initPlayList()
{
m_videos.append("/path/to/video1"); //这里的播放列表需要替换成你期望的地址(本地文件)
m_videos.append("/path/to/video2"); //这里的播放列表需要替换成你期望的地址(本地文件)
m_videos.append("/path/to/video3"); //这里的播放列表需要替换成你期望的地址(本地文件)
m_videos.append("/path/to/video4"); //这里的播放列表需要替换成你期望的地址(本地文件)
}
void MainWindow::play()
{
if(m_videos.isEmpty()) return;
m_curIdx = random() % m_videos.size();
m_curPlay = m_videos[m_curIdx];
m_player->setStateStop();
m_player->setPlay(m_curPlay);
}
#ifndef GST_PLAYER_H__
#define GST_PLAYER_H__
#include <QWidget>
#include <QString>
#include <QQueue>
#include <QVector>
#include <gst/gst.h>
class GstPlayer : public QObject
{
Q_OBJECT
public:
explicit GstPlayer(WId wid);
~GstPlayer();
static void gstInit();
signals:
void sig_ready();
void sig_pause();
void sig_play();
void sig_stop();
void sig_eos();
void sig_error(QString errMsg);
public:
/* @param uri: /path/to/video */
void setPlay(const QString &uri);
void setStateReady();
void setStateStop();
void setStatePause();
void setStatePlay();
void seek(int secs);
gint64 getPosition();
gint64 getDuration();
private:
static gboolean busMessageCb(GstBus *bus, GstMessage *msg, gpointer data);
static gboolean playIdleCb(gpointer data);
static gboolean stopIdleCb(gpointer data);
static void errCb(GstBus *bus, GstMessage *msg, GstPlayer *data);
static void eosCb(GstBus *bus, GstMessage *msg, GstPlayer *data);
static void stateChangeCb(GstBus *bus, GstMessage *msg, GstPlayer *data);
static void padAddedCb (GstElement *src, GstPad *newPad,GstPlayer *data);
gboolean playPipeline(const QString &uri);
void createPipeline();
void freePipeline();
GstElement *createPlaySink();
void releaseElement();
void evaluateState(GstState oldstate,GstState targetstate,GstState pendingstate);
void playFileInternal( );
void changeState(GstState newstate);
private:
WId m_wid;
GstElement *m_pipeline;
GstElement *m_filesrc;
GstElement *m_decode;
GstElement *m_playSink;
GstBus *m_bus;
QVector<GstPad *> m_pad_vec;
GstState m_curState;
GstState m_tarState;
QString m_curFile;
gboolean m_busyFlag;
gboolean m_seeking;
};
#endif
#include "gstplayer.h"
#include <gst/video/videooverlay.h>
#include <gst/gstinfo.h>
#include <gst/audio/streamvolume.h>
#define GST_PLAYER(point) ((GstPlayer *)(point))
#define RETURN_IF_FAIL(flag) do{ if(true == (flag)) return; }while(0);
GstPlayer::GstPlayer(WId wid)
: m_wid(wid)
, m_pipeline(nullptr)
, m_filesrc(nullptr)
, m_decode(nullptr)
, m_playSink(nullptr)
, m_bus(nullptr)
, m_curState(GST_STATE_VOID_PENDING)
, m_tarState(GST_STATE_VOID_PENDING)
, m_busyFlag(false)
, m_seeking(false)
{
createPipeline();
}
GstPlayer::~GstPlayer()
{
freePipeline();
}
//
void GstPlayer::gstInit()
{
gst_init (0, 0);
}
void GstPlayer::freePipeline()
{
if(m_pipeline)
{
gst_element_set_state (m_pipeline, GST_STATE_NULL);
releaseElement();
gst_object_unref (m_pipeline);
gst_bus_remove_signal_watch (m_bus);
gst_object_unref (m_bus);
}
}
void GstPlayer::createPipeline( )
{
m_pipeline = gst_pipeline_new (nullptr);
m_bus = gst_pipeline_get_bus (GST_PIPELINE (m_pipeline));
gst_bus_add_signal_watch (m_bus);
g_signal_connect (G_OBJECT (m_bus), "message", G_CALLBACK(busMessageCb), this);
}
gboolean GstPlayer::playIdleCb(gpointer data)
{
GstPlayer *gst;
gst = GST_PLAYER(data);
if (gst->m_curState < GST_STATE_PAUSED )
{
gst->playFileInternal( );
}
else
{
gst->changeState(GST_STATE_NULL);
}
return false;
}
gboolean GstPlayer::stopIdleCb(gpointer data)
{
GstPlayer *gst;
gst = GST_PLAYER(data);
gst->changeState(GST_STATE_NULL);
gst->releaseElement();
return false;
}
gboolean GstPlayer::busMessageCb(GstBus *bus, GstMessage *msg, gpointer data)
{
GstPlayer *gst = GST_PLAYER(data);
switch (GST_MESSAGE_TYPE(msg))
{
case GST_MESSAGE_EOS:
{
gst->m_busyFlag = false;
emit gst->sig_eos();
break;
}
case GST_MESSAGE_ERROR:
{
GError *error = nullptr;
gchar *debug;
gst->m_busyFlag = false;
gst->m_tarState = GST_STATE_NULL;
gst->changeState(GST_STATE_NULL);
gst_message_parse_error(msg, &error, &debug);
QString errStr = error->message;
emit gst->sig_error(errStr);
g_error_free(error);
g_free(debug);
break;
}
case GST_MESSAGE_STATE_CHANGED:
{
GstState oldstate, newstate, pendingstate;
gst_message_parse_state_changed (msg, &oldstate, &newstate, &pendingstate);
if (GST_MESSAGE_SRC(msg) == GST_OBJECT(gst->m_pipeline))
{
gst->evaluateState(oldstate, newstate, pendingstate);
}
break;
}
case GST_MESSAGE_ASYNC_DONE:
{
if (gst->m_seeking)
{
gst->m_seeking = false;
}
break;
}
default:
break;
}
return true;
}
void GstPlayer::padAddedCb(GstElement *src, GstPad *new_pad,GstPlayer *data)
{
GstCaps *new_pad_caps = nullptr;
GstStructure *new_pad_struct = nullptr;
GstElementClass *klass = nullptr;
GstPadTemplate *templ = nullptr;
const gchar *new_pad_type = nullptr;
if((new_pad_caps = gst_pad_get_current_caps (new_pad)) == nullptr)
{
return;
}
if((new_pad_struct = gst_caps_get_structure (new_pad_caps, 0)) == nullptr)
{
return;
}
if((new_pad_type = gst_structure_get_name (new_pad_struct)) == nullptr)
{
return;
}
klass = GST_ELEMENT_GET_CLASS(data->m_playSink);
if (g_str_has_prefix (new_pad_type, "audio/x-raw"))
{
templ = gst_element_class_get_pad_template(klass,"audio_sink");
}
else if(g_str_has_prefix (new_pad_type, "video/x-raw")) {
templ = gst_element_class_get_pad_template(klass,"video_sink");
}
else if(g_str_has_prefix (new_pad_type, "text"))
{
templ = gst_element_class_get_pad_template(klass,"text_sink");
}
if(templ)
{
GstPad *req_pad = gst_element_request_pad(data->m_playSink,templ,nullptr,nullptr);
if(!gst_pad_is_linked(req_pad))
{
gst_pad_link(new_pad,req_pad);
data->m_pad_vec.push_back(req_pad);
}
}
gst_caps_unref (new_pad_caps);
}
void GstPlayer::evaluateState(GstState oldstate,GstState targetstate,GstState pendingstate)
{
m_curState = targetstate;
if (m_tarState == m_curState )
{
m_busyFlag = false;
}
switch (m_curState) {
case GST_STATE_PLAYING:
{
emit sig_play();
break;
}
case GST_STATE_PAUSED:
{
emit sig_pause();
break;
}
case GST_STATE_READY:
{
m_seeking = false;
emit sig_ready();
break;
}
case GST_STATE_NULL:
{
m_seeking = false;
emit sig_stop();
break;
}
default:
break;
}
}
/* change gst state */
void GstPlayer::changeState(GstState newstate)
{
GstStateChangeReturn ret;
ret = gst_element_set_state (GST_ELEMENT(m_pipeline), newstate);
switch (ret)
{
case GST_STATE_CHANGE_SUCCESS:
evaluateState(GST_STATE(m_pipeline),
GST_STATE_TARGET(m_pipeline),
GST_STATE_PENDING(m_pipeline));
break;
case GST_STATE_CHANGE_ASYNC:
break;
case GST_STATE_CHANGE_FAILURE:
break;
case GST_STATE_CHANGE_NO_PREROLL:
break;
default:
break;
}
}
GstElement *GstPlayer::createPlaySink()
{
GstElement *play_sink = gst_element_factory_make ("playsink", "playsink");
if(nullptr == play_sink)
{
return nullptr;
}
gst_util_set_object_arg (G_OBJECT (play_sink), "flags","text+audio+video+soft-volume");
GstElement *video_sink = gst_element_factory_make ("xvimagesink", "vsink");
if(nullptr == video_sink)
{
if(nullptr == (video_sink = gst_element_factory_make ("ximagesink", "vsink")))
{
g_object_unref(play_sink);
return nullptr;
}
}
g_object_set(G_OBJECT(play_sink),"video-sink",video_sink, nullptr);
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (video_sink), m_wid);
return play_sink;
}
gboolean GstPlayer::playPipeline(const QString &uri )
{
m_filesrc = gst_element_factory_make ("filesrc", "filesrc");
m_decode = gst_element_factory_make ("decodebin", "decode");
m_playSink = createPlaySink();
if(!m_filesrc || !m_decode || !m_playSink)
{
if(m_filesrc) { g_object_unref(m_filesrc) ; m_filesrc = nullptr;}
if(m_decode) { g_object_unref(m_decode) ; m_decode = nullptr;}
if(m_playSink) { g_object_unref(m_playSink); m_playSink = nullptr;}
return false;
}
gst_bin_add_many (GST_BIN (m_pipeline),m_filesrc,m_decode, m_playSink,nullptr);
g_object_set(G_OBJECT(m_filesrc),"location",uri.toStdString().c_str(), nullptr);
gboolean linkRet = gst_element_link(m_filesrc,m_decode);
if (!linkRet)
{
gst_bin_remove(GST_BIN(m_pipeline),m_filesrc);
gst_bin_remove(GST_BIN(m_pipeline),m_decode);
gst_bin_remove(GST_BIN(m_pipeline),m_playSink);
m_filesrc = nullptr;
m_decode = nullptr;
m_playSink = nullptr;
return false;
}
g_signal_connect (m_decode, "pad-added", G_CALLBACK (padAddedCb), this);
return true;
}
void GstPlayer::playFileInternal( )
{
if (G_UNLIKELY(GST_STATE(m_pipeline) == GST_STATE_PLAYING))
{
//
}
bool ret = playPipeline(m_curFile);
if(ret)
{
changeState(GST_STATE_PLAYING);
}
else
{
changeState(GST_STATE_NULL);
}
}
void GstPlayer::setPlay(const QString &uri)
{
RETURN_IF_FAIL(m_busyFlag)
if(uri.isEmpty())
{
return;
}
m_curFile = uri;
m_tarState = GST_STATE_PLAYING;
g_idle_add((GSourceFunc) playIdleCb, this);
return;
}
void GstPlayer::setStateReady()
{
RETURN_IF_FAIL(m_busyFlag)
m_tarState = GST_STATE_READY;
changeState(GST_STATE_READY);
}
void GstPlayer::setStatePause()
{
RETURN_IF_FAIL(m_busyFlag)
m_tarState = GST_STATE_PAUSED;
m_busyFlag = true;
changeState(GST_STATE_PAUSED);
}
void GstPlayer::setStateStop()
{
RETURN_IF_FAIL(m_busyFlag)
m_tarState = GST_STATE_NULL;
g_idle_add((GSourceFunc) stopIdleCb, this);
}
void GstPlayer::setStatePlay()
{
RETURN_IF_FAIL(m_busyFlag)
m_tarState = GST_STATE_PLAYING;
changeState(GST_STATE_PLAYING);
}
gint64 GstPlayer::getDuration()
{
gint64 absolute_duration = 0;
gint64 duration = 0;
GstFormat gst_time = GST_FORMAT_TIME;
gst_element_query_duration(m_pipeline, gst_time, &absolute_duration);
if (absolute_duration < 0)
{
absolute_duration = 0;
}
if (gst_time == GST_FORMAT_TIME)
{
duration = absolute_duration / GST_SECOND;
}
return duration;
}
gint64 GstPlayer::getPosition( )
{
GstFormat format = GST_FORMAT_TIME;
gint64 pos;
gst_element_query_position(m_pipeline, format, &pos);
return pos / GST_SECOND;
}
void GstPlayer::seek(int secs)
{
gint64 seekPos = secs * GST_SECOND;
if (!gst_element_seek_simple( m_pipeline, GST_FORMAT_TIME,(GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),seekPos))
{
return;
}
m_seeking = true;
}
void GstPlayer::releaseElement()
{
if(m_playSink && !m_pad_vec.empty())
{
for(int i =0; i < m_pad_vec.size(); ++i)
{
gst_element_release_request_pad(m_playSink,m_pad_vec[i]);
gst_object_unref(m_pad_vec[i]);
}
m_pad_vec.clear();
}
if(m_playSink)
{
gst_element_set_state (GST_ELEMENT(m_playSink), GST_STATE_NULL);
gst_bin_remove(GST_BIN(m_pipeline),m_playSink);
m_playSink = nullptr;
}
if(m_filesrc)
{
gst_element_set_state (GST_ELEMENT(m_filesrc), GST_STATE_NULL);
gst_bin_remove(GST_BIN(m_pipeline),m_filesrc);
m_filesrc = nullptr;
}
if(m_decode)
{
gst_element_set_state (GST_ELEMENT(m_decode), GST_STATE_NULL);
gst_bin_remove(GST_BIN(m_pipeline),m_decode);
m_decode = nullptr;
}
}