搞了好久,有的bug都不知道怎么回事,一下外部源错误一些异常退出,虽然还有很多的不足和课改进之处,基本功能是实现了。错误很大原因是element选择的不对,有的decode element在解码之前是需要parse element来解析的。还有要注意在使用“动态管道”选择demuxer分流后的source pad使之与后面的element的sink pad连接的时候,要根据具体情况改一下具体代码,才能成功连接。
调取源流,网络(http)视频的source element用souphttpsrc,本地视频用filesrc。
代码:
main.c
/* 播放格式:容器 Matroska
* 视频格式:MPEG
* 音频格式:AAC*/
#include <glib.h>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
#include "qtoverlay.h"
#include <QApplication>
#include <QWidget>
#include <QSlider>
#include <QLabel>
#include <QObject>
#define MKV_SOURCE "/home/xqh/桌面/QT-object/OBJECT4/testvideo.mkv"
/*所有数据放在一个结构体内,方便调用*/
typedef struct _ST_CUSTOMDATA {
GstElement *pipeline;
GstElement *mediaSource;
GstElement *mediaDemuxer;
GstElement *audioQueue;
GstElement *videoQueue;
GstElement *videoParse;
GstElement *audioDecoder;
GstElement *videoDecoder;
GstElement *audioConvert;
GstElement *videoConvert;
GstElement *audioSink;
GstElement *videoSink;
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 pad_added_handler (GstElement *src, GstPad *new_pad, ST_CUSTOMDATA *data) {
GstPad *sink_pad_video = gst_element_get_static_pad (data->videoQueue, "sink");
GstPad *sink_pad_audio = gst_element_get_static_pad (data->audioQueue, "sink");
GstPadLinkReturn ret_video;
GstPadLinkReturn ret_audio;
GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL;
g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));
/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps (new_pad);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad_video)) {
g_print ("We are already linked. Ignoring.\n");
goto audio;
}
if (!g_str_has_prefix (new_pad_type, "video/mpeg")) {
g_print ("It has type '%s' which is not video/mpeg. Ignoring.\n", new_pad_type);
goto audio;
}
/* Attempt the link */
ret_video = gst_pad_link (new_pad, sink_pad_video);
if (GST_PAD_LINK_FAILED (ret_video)) {
g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print ("Link succeeded (type '%s').\n", new_pad_type);
}
audio:
if (!g_str_has_prefix (new_pad_type, "audio/mpeg")) {
g_print ("It has type '%s' which is not audio/mpeg. Ignoring.\n", new_pad_type);
goto exit;
}
/* Attempt the link */
ret_audio = gst_pad_link (new_pad, sink_pad_audio);
if (GST_PAD_LINK_FAILED (ret_audio)) {
g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print ("Link succeeded (type '%s').\n", new_pad_type);
}
exit:
/* Unreference the new pad's caps, if we got them */
if (new_pad_caps != NULL)
gst_caps_unref (new_pad_caps);
/* Unreference the sink pad */
gst_object_unref (sink_pad_video);
gst_object_unref (sink_pad_audio);
}
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()));
/*******************
创建element
*******************/
customData.pipeline = gst_pipeline_new("mp4-player");
//customData.mediaSource = gst_element_factory_make("souphttpsrc", "file-source"); 网页信息源
customData.mediaSource = gst_element_factory_make("filesrc","file-source") ;
customData.mediaDemuxer = gst_element_factory_make("matroskademux", "matroska-demuxer");
// create video elements
customData.videoQueue = gst_element_factory_make ("queue", "video-queue");
// customData.videoParse = gst_element_factory_make("theoraparse","video-parse");
customData.videoDecoder = gst_element_factory_make("avdec_mpeg4", "video-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", "audio-decoder");
customData.audioConvert = gst_element_factory_make("audioconvert", "audio-converter");
customData.audioSink = gst_element_factory_make("autoaudiosink", "audio-output");
if (!customData.pipeline || !customData.videoQueue ||
!customData.mediaSource || !customData.mediaDemuxer ||
!customData.videoQueue || !customData.videoDecoder ||
!customData.videoConvert || !customData.videoSink ||
!customData.audioQueue || !customData.audioDecoder ||
!customData.audioConvert || !customData.audioSink )
{
g_printerr ("One element could not be created. Exiting.\n");
return -1;
}
/*******************
添加并连接连接element
*******************/
gst_bin_add_many (GST_BIN (customData.pipeline),
customData.mediaSource, customData.mediaDemuxer,
customData.videoQueue, customData.videoDecoder,
customData.videoConvert, customData.videoSink,
customData.audioQueue, customData.audioDecoder,
customData.audioConvert, customData.audioSink,
NULL);
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,NULL);
g_signal_connect (customData.mediaDemuxer, "pad-added", G_CALLBACK (pad_added_handler), &customData);
/*******************
新建播放窗口
*******************/
PlayerWindow *window = new PlayerWindow(customData.pipeline);
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.pipeline), "video-sink", customData.videoSink, NULL);
/*******************
设置播放源
*******************/
g_object_set (G_OBJECT(customData.mediaSource), "location", MKV_SOURCE, NULL);
/*******************
信息处理
*******************/
bus = gst_pipeline_get_bus (GST_PIPELINE (customData.pipeline));
bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
gst_object_unref (bus);
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.pipeline, 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.pipeline, GST_STATE_NULL);
int ret = app.exec();
window->hide();
g_print ("Deleting pipeline\n");
gst_object_unref (GST_OBJECT (customData.pipeline));
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);
return ret;
}
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();
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 PlayerWindow::getVideoWId() const {
return videoWindow->winId();
}
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) {
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;
}
qtoverlay.h:
#ifndef QTOVERLAY_H
#define QTOVERLAY_H
#endif // 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();
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
.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TEMPLATE = app
CONFIG += console
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
INCLUDEPATH += \
/usr/include/glib-2.0 \
/usr/include/glib-2.0/glib \
/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 += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.C \
qtoverlay.cpp
HEADERS += \
qtoverlay.h
FORMS +=
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target