From 9524fff66d6c7f483ff93045960071f7daaee139 Mon Sep 17 00:00:00 2001 From: Benjamin Sigonneau Date: Mon, 25 Mar 2019 14:57:47 +0100 Subject: [PATCH] Initial commit --- .gitignore | 7 + main.cpp | 23 ++++ main.pro | 14 ++ videoplayer.cpp | 346 ++++++++++++++++++++++++++++++++++++++++++++++++ videoplayer.h | 58 ++++++++ 5 files changed, 448 insertions(+) create mode 100644 .gitignore create mode 100644 main.cpp create mode 100644 main.pro create mode 100644 videoplayer.cpp create mode 100644 videoplayer.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..437d572 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +moc_* +.qmake.stash +Makefile +main +main.pro.user +*.mp4 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..76e4674 --- /dev/null +++ b/main.cpp @@ -0,0 +1,23 @@ +#include "videoplayer.h" + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + QCoreApplication::setApplicationName("XTrack-NG core POC"); + QCommandLineParser parser; + parser.setApplicationDescription("XTrack-NG core POC"); + parser.addHelpOption(); + parser.process(app); + + VideoPlayer player; + + player.show(); + + return app.exec(); +} diff --git a/main.pro b/main.pro new file mode 100644 index 0000000..5cf1d6e --- /dev/null +++ b/main.pro @@ -0,0 +1,14 @@ +TEMPLATE = app +load(ccache) + +QT += widgets + +HEADERS += videoplayer.h + +SOURCES += main.cpp \ + videoplayer.cpp + +unix { + CONFIG += link_pkgconfig + PKGCONFIG += gstreamer-1.0 gstreamer-video-1.0 +} diff --git a/videoplayer.cpp b/videoplayer.cpp new file mode 100644 index 0000000..3e458a0 --- /dev/null +++ b/videoplayer.cpp @@ -0,0 +1,346 @@ +#include "videoplayer.h" + +#include + +#include +#include +#include +#include + + +static void +pad_added_cb (GstElement *src, GstPad *srcpad, GstElement *peer) +{ + g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (srcpad), GST_ELEMENT_NAME (src)); + + (void)src; // remove unused variable warning + GstPad *sinkpad = gst_element_get_static_pad (peer, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (sinkpad); +} + + +void VideoPlayer::print_status_of_all() +{ + auto it = gst_bin_iterate_elements(GST_BIN(pipeline)); + GValue value = G_VALUE_INIT; + for (GstIteratorResult r = gst_iterator_next(it, &value); + r != GST_ITERATOR_DONE; + r = gst_iterator_next(it, &value)) + { + if (r == GST_ITERATOR_OK) + { + GstElement *e = static_cast(g_value_peek_pointer(&value)); + GstState current, pending; + gst_element_get_state(e, ¤t, &pending, 100000); + g_print("%s(%s), status = %s, pending = %s\n", G_VALUE_TYPE_NAME(&value), gst_element_get_name(e), gst_element_state_get_name(current), gst_element_state_get_name(pending)); + } + } +} + +VideoPlayer::VideoPlayer(QWidget *parent) + : QWidget(parent) +{ + QAbstractButton *debugButton = new QPushButton(tr("?")); + connect(debugButton, SIGNAL(clicked()), this, SLOT(debugSlot())); + + m_stopButton = new QPushButton(this); + m_stopButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); + m_stopButton->setEnabled(false); + connect(m_stopButton, SIGNAL(clicked()), this, SLOT(stop())); + + m_playButton = new QPushButton(this); + m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + connect(m_playButton, &QAbstractButton::clicked, this, &VideoPlayer::playPause); + + + // ----------------------------------------------------------------- + // Options + QCheckBox *tcpCheckBox = new QCheckBox("Use TCP protocol", this); + tcpCheckBox->setChecked(use_tcp); + connect(tcpCheckBox, SIGNAL(toggled(bool)), this, SLOT(useTcp(bool))); + + QLineEdit *rtspUrlEdit = new QLineEdit(this); + connect (rtspUrlEdit, SIGNAL(textEdited(QString)), this, SLOT(setRtspUrl(QString))); + //rtspUrlEdit->setText("rtsp://127.0.0.1:8554/test"); + rtspUrlEdit->setText(rtspUrl); + + QFormLayout *optionsLayout = new QFormLayout(); + optionsLayout->addWidget(tcpCheckBox); + optionsLayout->addRow("Rtsp URL", rtspUrlEdit); + + // ----------------------------------------------------------------- + // Player controls + QBoxLayout *controlLayout = new QHBoxLayout; + controlLayout->setMargin(0); + controlLayout->addWidget(debugButton); + controlLayout->addWidget(m_stopButton); + controlLayout->addWidget(m_playButton); + controlLayout->addStretch(); + + playerWidget = new QWidget(this); + + QBoxLayout *layout = new QVBoxLayout(this); + layout->addLayout(optionsLayout); + layout->addWidget(playerWidget); + layout->addLayout(controlLayout); + +} + +//void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) +void cb_message (GstBus *bus, GstMessage *msg, GstElement *pipeline) +{ + (void)bus; // remove unused variable warning + int buffering_level = 0; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_BUFFERING: + gst_message_parse_buffering (msg, &buffering_level); + qDebug() << "Buffering level: " << buffering_level; + /* Wait until buffering is complete before start/resume playing */ + if (buffering_level < 100) { + gst_element_set_state (pipeline, GST_STATE_PAUSED); + } else { + gst_element_set_state (pipeline, GST_STATE_PLAYING); + } + break; + default: + /* Unhandled message */ + break; + } + return; +} + +VideoPlayer::~VideoPlayer() +{ + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +void VideoPlayer::initGst () { + // prepare the pipeline + gst_init (NULL, NULL); + pipeline = gst_pipeline_new ("plop"); + + GstBus *bus = gst_element_get_bus (pipeline); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (cb_message), pipeline); +} + +void VideoPlayer::setGstTestVideo () { + setGstTestVideo (0); +} + +void VideoPlayer::setGstTestVideo (int pattern) +{ + initGst(); + + gst_element_set_state (pipeline, GST_STATE_PAUSED); + + GstElement *src = gst_element_factory_make ("videotestsrc", NULL); + GstElement *time = gst_element_factory_make ("timeoverlay", NULL); + GstElement *sink = gst_element_factory_make ("xvimagesink", NULL); + gst_bin_add_many (GST_BIN (pipeline), src, time, sink, NULL); + gst_element_link_many (src, time, sink, NULL); + g_object_set (src, "pattern", pattern, NULL); + g_object_set (time, "font-desc", "Sans, 24", NULL); + + WId xwinid = playerWidget->winId(); + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), xwinid); + + // Need to set newly added elements in the same state as the pipeline + gst_element_set_state (src, GST_STATE_PAUSED); + gst_element_set_state (time, GST_STATE_PAUSED); + gst_element_set_state (sink, GST_STATE_PAUSED); + + return; +} + + +void VideoPlayer::setGstFileVideo () +{ + qDebug() << "[setGstFileVideo] Entering function"; + initGst(); + + GstElement *src = gst_element_factory_make ("filesrc", NULL); + GstElement *demux = gst_element_factory_make ("qtdemux", NULL); + GstElement *vdec = gst_element_factory_make ("avdec_h264", NULL); + GstElement *vqueue = gst_element_factory_make ("queue2", NULL); + GstElement *vconv = gst_element_factory_make ("videoconvert", NULL); + GstElement *time = gst_element_factory_make ("timeoverlay", NULL); + GstElement *sink = gst_element_factory_make ("xvimagesink", NULL); + video_sink = sink; + + // qDebug () << "[setGstFileVideo] rtspUrl = " << rtspUrl; + g_object_set (src, "location", "sintel-trailer.mp4", NULL); + g_object_set (time, "font-desc", "Sans, 24", NULL); + //g_object_set (vqueue, "ring-buffer-max-size", (guint64)40000000, NULL); + g_object_set (vqueue, + "max-size-bytes", (guint64)128 * 1024 * 1024, // 128 MB buffer + //"ring-buffer-max-size", (guint64)128 * 1024, // 128 KB buffer + "use-buffering", true, + NULL); + + // if (!rate_control) { + // g_object_set (src, "onvif-rate-control", FALSE, NULL); + // } + + // if (use_tcp) { + // g_object_set (src, "protocols", GST_RTSP_LOWER_TRANS_TCP, NULL); + // } + + gst_bin_add_many (GST_BIN (pipeline), src, demux, vdec, vqueue, vconv, time, sink, NULL); + + gst_element_link_many (src, demux, NULL); + // link demux to vdec in a cb, when pad is created + g_signal_connect (demux, "pad-added", G_CALLBACK (pad_added_cb), vdec); + // link rest of the pipeline + gst_element_link_many (vdec, vqueue, vconv, time, sink, NULL); + + WId xwinid = playerWidget->winId(); + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), xwinid); + + gst_element_set_state (pipeline, GST_STATE_PAUSED); + qDebug() << "[setGstFileVideo] All done, pipeline is in PAUSED state"; + + return; +} + + + +void VideoPlayer::setGstRtspVideo () +{ + initGst(); + + GstElement *src = gst_element_factory_make ("rtspsrc", NULL); + GstElement *vdepay = gst_element_factory_make ("rtph264depay", NULL); + GstElement *vdec = gst_element_factory_make ("avdec_h264", NULL); + GstElement *vqueue = gst_element_factory_make ("queue2", NULL); + GstElement *vconv = gst_element_factory_make ("videoconvert", NULL); + GstElement *time = gst_element_factory_make ("timeoverlay", NULL); + GstElement *sink = gst_element_factory_make ("xvimagesink", NULL); + + //g_object_set (src, "location", "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov", NULL); + qDebug () << "[setGstRtspVideo] rtspUrl = " << rtspUrl; + g_object_set (src, "location", rtspUrl.toStdString().c_str(), NULL); + g_object_set (time, "font-desc", "Sans, 24", NULL); + g_object_set (vqueue, "max-size-bytes", (guint64)40000000, NULL); + + if (use_tcp) { + g_object_set (src, "protocols", GST_RTSP_LOWER_TRANS_TCP, NULL); + } + + gst_bin_add_many (GST_BIN (pipeline), src, vdepay, vdec, vqueue, vconv, time, sink, NULL); + // link src to vdepay in a cb, when pad is created + g_signal_connect (src, "pad-added", G_CALLBACK (pad_added_cb), vdepay); + // link rest of the pipeline + gst_element_link_many (vdepay, vdec, vqueue, vconv, time, sink, NULL); + + WId xwinid = playerWidget->winId(); + gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink), xwinid); + + gst_element_set_state (pipeline, GST_STATE_PAUSED); + + return; +} + + +void VideoPlayer::play() +{ + if (!pipeline) { + setGstRtspVideo(); + //setGstFileVideo(); + //setGstTestVideo(); + } + + // 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())); + } + m_stopButton->setEnabled(true); + playing = true; + return; +} + +void VideoPlayer::pause() +{ + qDebug() << "PAUSE called"; + //GST_DEBUG_BIN_TO_DOT_FILE((GstBin *)pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "pipeline"); + GstStateChangeReturn sret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + 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())); + } + playing = false; + return; +} + +void VideoPlayer::playPause() +{ + if (playing) { + qDebug() << "toggle: play --> pause"; + m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + VideoPlayer::pause(); + } else { + qDebug() << "toggle: pause --> play"; + m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPause)); + //m_playButton->setEnabled(false); + VideoPlayer::play(); + } + // playing boolean is set in play and pause methods + return; +} + +void VideoPlayer::stop() +{ + qDebug() << "[STOP function] called"; + + if (pipeline) { + qDebug() << "[STOP function] detected a running pipeline"; + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + pipeline = nullptr; + qDebug() << "[STOP function] stopped and dropped pipeline"; + } else { + qDebug() << "[STOP function] no running pipeline"; + } + + // Reinit interface + playing = false; + m_playButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + playerWidget->repaint(); + m_stopButton->setEnabled(false); + + return; +} + + + +void VideoPlayer::debugSlot() +{ + qDebug() << "DEBUG"; + print_status_of_all(); + qDebug() << "Use TCP? " << use_tcp; +} + +void VideoPlayer::useTcp(bool value) +{ + use_tcp = value; +} + +void VideoPlayer::setRtspUrl(QString url) +{ + qDebug() << "setRtspUrl: " << url; + rtspUrl = url; +} + +void VideoPlayer::benSlot() +{ + qDebug() << " ben slot..."; +} diff --git a/videoplayer.h b/videoplayer.h new file mode 100644 index 0000000..ca69980 --- /dev/null +++ b/videoplayer.h @@ -0,0 +1,58 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QAbstractButton; +QT_END_NAMESPACE + +class VideoPlayer : public QWidget +{ + Q_OBJECT + +public: + VideoPlayer(QWidget *parent = nullptr); + ~VideoPlayer(); + + void load(const QUrl &url); + +public slots: + void play(); + void pause(); + void playPause(); + void stop(); + +private slots: + void debugSlot(); + void useTcp(bool value); + void setRtspUrl(QString url); + void benSlot(); + +private: + QList videoDisplays = {}; + QWidget *playerWidget = nullptr; + + QAbstractButton *m_stopButton = nullptr; + QAbstractButton *m_playButton = nullptr; + QAbstractButton *m_seekBackwardButton = nullptr; + QAbstractButton *m_seekForwardButton = nullptr; + bool playing = false; + GstElement *pipeline = nullptr; + GstElement *video_sink = nullptr; + + bool use_tcp = true; + QString rtspUrl = "rtsp://127.0.0.1:8554/test"; + + void print_status_of_all(); + + void initGst(); + void setGstTestVideo(); + void setGstTestVideo(int pattern); + void setGstFileVideo(); + void setGstRtspVideo(); +}; + +#endif