基于上一篇GStreamer开发简单MP4播放器(一)的基础上,在官网上找到了GStreamer和QT联合使用的案例,于是copy了部分代码,调通了暂停,seek功能,本来向实现个音量调节,但是发现案例都是基于playbin的,暂时没找到解决方案,先把部分代码贴上来。
1 暂停和停止播放功能
void PlayerWindow::onPauseClicked() {
gst_element_set_state (pipeline, GST_STATE_PAUSED);
}
void PlayerWindow::onStopClicked() {
gst_element_set_state (pipeline, GST_STATE_NULL);
}
2 seek功能实现
void PlayerWindow::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, ¤t)) {
g_print("%ld / %ld\n", current/GST_SECOND, totalDuration/GST_SECOND);
slider->setValue(current/GST_SECOND);
}
}
}
void PlayerWindow::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);
}
3 最终效果
4 完整代码
main.cpp
#include <glib.h>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
#include "qtoverlay.h"
//#include "mainwindow.h"
#include <QApplication>
#include <QWidget>
#include <QSlider>
#include <QLabel>
#include <QObject>
#define MP4_SOURCE "./movie1.mp4"
typedef struct _ST_CUSTOMDATA {
GstElement *gstpPipeline;
GstElement *mediaSource;
GstElement *mediaDemuxer;
GstElement *audioQueue;
GstElement *videoQueue;
GstElement *audioDecoder;
GstElement *videoDecoder;
GstElement *audioConvert;
GstElement *videoConvert;
GstElement *audioSink;
GstElement *videoSink;
GstElement *mediaVolume;
QFrame *movieFrame;
QSlider *timeSlider;
QSlider *volumeSlider;
QLabel *curTimeLabel;
QLabel *durTimeLabel;
GstState pipelineState; /* Current state of the pipeline */
gint64 mediaDuration; /* Duration of the clip, in nanoseconds */
} ST_CUSTOMDATA ;
ST_CUSTOMDATA customData;
static gboolean bus_call (GstBus *bus, GstMessage *msg, gpointer data)
{
GMainLoop *loop = (GMainLoop *) data;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_EOS:
g_print ("End of stream\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *error;
gst_message_parse_error (msg, &error, &debug);
g_free (debug);
g_printerr ("Error: %s\n", error->message);
g_error_free (error);
g_main_loop_quit (loop);
break;
}
default:
break;
}
return TRUE;
}
static void on_pad_added (GstElement *element,GstPad *pad, ST_CUSTOMDATA *data)
{
gchar *pad_name = NULL;
g_object_get(pad, "name", &pad_name, NULL);
if(g_str_has_prefix(pad_name, "video"))
{
GstPad *v_sinkpad = NULL;
v_sinkpad = gst_element_get_static_pad (data->videoQueue, "sink");
gst_pad_link (pad, v_sinkpad);
gst_object_unref (v_sinkpad);
}
if(g_str_has_prefix(pad_name, "audio"))
{
GstPad *a_sinkpad;
a_sinkpad = gst_element_get_static_pad (data->audioQueue, "sink");
if(!gst_pad_link (pad, a_sinkpad))
g_print("%s linked to adecoder.\n", pad_name);
else
g_print("ERROR: gst_pad_link (pad, a_sinkpad), pad_name = %s\n",pad_name);
gst_object_unref (a_sinkpad);
}
}
int main(int argc, char *argv[])
{
GMainLoop *loop;
GstBus *bus;
guint bus_watch_id;
// Initialisation
gst_init (&argc, &argv);
loop = g_main_loop_new(NULL, FALSE);
QApplication app(argc, argv);
app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit ()));
customData.gstpPipeline = gst_pipeline_new("mp4-player");
customData.mediaSource = gst_element_factory_make("filesrc", "file-source");
customData.mediaDemuxer = gst_element_factory_make("qtdemux", "qt-demuxer");
// create video elements
customData.videoQueue = gst_element_factory_make ("queue", "video-queue");
customData.videoDecoder = gst_element_factory_make("avdec_h264", "h264-decoder");
customData.videoConvert = gst_element_factory_make ("videoconvert", "video-converter");
customData.videoSink = gst_element_factory_make ("ximagesink", "video-output");
// create audio elements
customData.audioQueue = gst_element_factory_make ("queue", "audio-queue");
customData.audioDecoder = gst_element_factory_make("faad", "au-decoder");
customData.audioConvert = gst_element_factory_make("audioconvert", "au-converter");
customData.audioSink = gst_element_factory_make("autoaudiosink", "au-output");
customData.mediaVolume = gst_element_factory_make("volume", NULL);
double m_volume = 0.1;
g_object_set(G_OBJECT(customData.mediaVolume), "volume", m_volume, NULL);
if (!customData.gstpPipeline || !customData.videoQueue ||
!customData.mediaSource || !customData.mediaDemuxer ||
!customData.videoQueue || !customData.videoDecoder ||
!customData.videoConvert || !customData.videoSink ||
!customData.audioQueue || !customData.audioDecoder ||
!customData.audioConvert || !customData.audioSink ||
!customData.mediaVolume)
{
g_printerr ("One element could not be created. Exiting.\n");
return -1;
}
PlayerWindow *window = new PlayerWindow(customData.gstpPipeline);
//window.setWindowTitle("Qt&GStreamer Simple Player");
window->resize(900, 600);
window->show();
WId xwinid = window->getVideoWId();
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (customData.videoSink), xwinid);
g_object_set(GST_OBJECT(customData.gstpPipeline), "video-sink", customData.videoSink, NULL);
// set the input filename to the source element
g_object_set (G_OBJECT(customData.mediaSource), "location", MP4_SOURCE, NULL);
//add a message handler
bus = gst_pipeline_get_bus (GST_PIPELINE (customData.gstpPipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
gst_object_unref (bus);
// add all elements into the pipeline
gst_bin_add_many (GST_BIN (customData.gstpPipeline),
customData.mediaSource, customData.mediaDemuxer,
customData.videoQueue, customData.videoDecoder,
customData.videoConvert, customData.videoSink,
customData.audioQueue, customData.audioDecoder,
customData.audioConvert, customData.audioSink,
customData.mediaVolume,
NULL);
// link the elements together
gst_element_link(customData.mediaSource, customData.mediaDemuxer);
gst_element_link_many(customData.videoQueue, customData.videoDecoder,
customData.videoConvert, customData.videoSink, NULL);
gst_element_link_many(customData.audioQueue, customData.audioDecoder,
customData.audioConvert,customData.audioSink,
customData.mediaVolume);
g_signal_connect (customData.mediaDemuxer, "pad-added", G_CALLBACK (on_pad_added), &customData);
gst_bus_add_watch(bus, &PlayerWindow::postGstMessage, window);
/* note that the demuxer will be linked to the decoder dynamically.
The reason is that Ogg may contain various streams (for example
audio and video). The source pad(s) will be created at run time,
by the demuxer when it detects the amount and nature of streams.
Therefore we connect a callback function which will be executed
when the "pad-added" is emitted.*/
// Set the pipeline to "playing" state
gst_element_set_state (customData.gstpPipeline, GST_STATE_PLAYING);
// Iterate
g_print ("Running...\n");
g_main_loop_run (loop);
// Out of the main loop, clean up nicely
g_print ("Returned, stopping playback\n");
gst_element_set_state (customData.gstpPipeline, GST_STATE_NULL);
int ret = app.exec();
window->hide();
g_print ("Deleting pipeline\n");
gst_object_unref (GST_OBJECT (customData.gstpPipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);
return ret;
}
qtoverlay.h
#ifndef _QTOVERLAY_
#define _QTOVERLAY_
#include <gst/gst.h>
#include <QWidget>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSlider>
#include <QTimer>
class PlayerWindow : public QWidget
{
Q_OBJECT
public:
PlayerWindow(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();
void setVolume();
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;
QSlider *volumeSlider;
GstState state;
gint64 totalDuration;
};
#endif
qtoverlay.cpp
#include <gst/video/videooverlay.h>
#include <QApplication>
#include "qtoverlay.h"
PlayerWindow::PlayerWindow(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();
volumeSlider = new QSlider(Qt::Horizontal);
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()));
connect(volumeSlider, SIGNAL(sliderReleased()), this, SLOT(setVolume()));
buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(playBt);
buttonLayout->addWidget(pauseBt);
buttonLayout->addWidget(stopBt);
buttonLayout->addWidget(slider);
buttonLayout->addWidget(volumeSlider);
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 PlayerWindow::getVideoWId() const {
return videoWindow->winId();
}
void PlayerWindow::setVolume()
{
double numVolume = volumeSlider->sliderPosition()/10;
if (numVolume < 0)
{
numVolume = 0;
}
else if (numVolume > 4.0)
{
numVolume = 4.0;
}
g_print("volume: %lf\n", numVolume);
g_object_set(pipeline, "volume", numVolume, NULL);
}
void PlayerWindow::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 PlayerWindow::onPauseClicked() {
gst_element_set_state (pipeline, GST_STATE_PAUSED);
}
void PlayerWindow::onStopClicked() {
gst_element_set_state (pipeline, GST_STATE_NULL);
}
void PlayerWindow::onAlbumAvaiable(const QString &album) {
setWindowTitle(album);
}
void PlayerWindow::onState(GstState st) {
if (state != st) {
state = st;
if (state == GST_STATE_PLAYING){
timer->start(1000);
}
if (state < GST_STATE_PAUSED){
timer->stop();
}
}
}
void PlayerWindow::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, ¤t)) {
g_print("%ld / %ld\n", current/GST_SECOND, totalDuration/GST_SECOND);
slider->setValue(current/GST_SECOND);
}
}
}
void PlayerWindow::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 PlayerWindow::onEos() {
gst_element_set_state (pipeline, GST_STATE_NULL);
}
gboolean PlayerWindow::postGstMessage(GstBus * bus, GstMessage * message, gpointer user_data) {
PlayerWindow *pw = NULL;
if (user_data) {
pw = reinterpret_cast<PlayerWindow*>(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;
}