虚拟机ubuntu下使用gstreamer推流学习笔记(四)简易的.mkv格式视频播放器

搞了好久,有的bug都不知道怎么回事,一下外部源错误一些异常退出,虽然还有很多的不足和课改进之处,基本功能是实现了。错误很大原因是element选择的不对,有的decode element在解码之前是需要parse element来解析的。还有要注意在使用“动态管道”选择demuxer分流后的source pad使之与后面的element的sink pad连接的时候,要根据具体情况改一下具体代码,才能成功连接。

调取源流,网络(http)视频的source element用souphttpsrc,本地视频用filesrc。

不同格式的测试视频:
网页:https://blog.csdn.net/xia296/article/details/118651949?ops_request_misc=&request_id=&biz_id=102&utm_term=mkv%20%20%E6%B5%8B%E8%AF%95%E5%9C%B0%E5%9D%80&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-118651949.pc_search_all_es&spm=1018.2226.3001.4187

下载:https://sample-videos.com/index.php#sample-mp4-video

代码:

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, &current)) {
            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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值