From 9565d5244a3f4eac8d682088996caebdebc89026 Mon Sep 17 00:00:00 2001 From: Jacob Walser Date: Fri, 13 Jan 2017 15:01:44 -0500 Subject: [PATCH] Add icon to main toolbar to start/stop recording videostream to file --- src/VideoStreaming/VideoReceiver.cc | 215 +++++++++++++++++++++-- src/VideoStreaming/VideoReceiver.h | 36 +++- src/ui/toolbar/MainToolBarIndicators.qml | 21 +++ 3 files changed, 253 insertions(+), 19 deletions(-) diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoStreaming/VideoReceiver.cc index 594a028f0..b12189cad 100644 --- a/src/VideoStreaming/VideoReceiver.cc +++ b/src/VideoStreaming/VideoReceiver.cc @@ -17,11 +17,136 @@ #include "VideoReceiver.h" #include #include +#include +#include + +VideoReceiver::Sink* VideoReceiver::sink = NULL; +GstElement* VideoReceiver::_pipeline = NULL; +GstElement* VideoReceiver::_pipeline2 = NULL; +GstElement* VideoReceiver::tee = NULL; + +gboolean VideoReceiver::_eosCB(GstBus* bus, GstMessage* message, gpointer user_data) +{ + Q_UNUSED(bus); + Q_UNUSED(message); + Q_UNUSED(user_data); + + gst_bin_remove(GST_BIN(_pipeline2), sink->queue); + gst_bin_remove(GST_BIN(_pipeline2), sink->mux); + gst_bin_remove(GST_BIN(_pipeline2), sink->filesink); + + gst_element_set_state(_pipeline2, GST_STATE_NULL); + gst_object_unref(_pipeline2); + + gst_element_set_state(sink->filesink, GST_STATE_NULL); + gst_element_set_state(sink->mux, GST_STATE_NULL); + gst_element_set_state(sink->queue, GST_STATE_NULL); + + gst_object_unref(sink->queue); + gst_object_unref(sink->mux); + gst_object_unref(sink->filesink); + + delete sink; + sink = NULL; + + return true; +} + +GstPadProbeReturn VideoReceiver::_unlinkCB(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) +{ + Q_UNUSED(pad); + Q_UNUSED(info); + Q_UNUSED(user_data); + + if(!g_atomic_int_compare_and_exchange(&sink->removing, FALSE, TRUE)) + return GST_PAD_PROBE_OK; + + GstPad* sinkpad = gst_element_get_static_pad(sink->queue, "sink"); + gst_pad_unlink (sink->teepad, sinkpad); + gst_object_unref (sinkpad); + + gst_element_release_request_pad(tee, sink->teepad); + gst_object_unref(sink->teepad); + + // Also unlinks and unrefs + gst_bin_remove (GST_BIN (_pipeline), sink->queue); + gst_bin_remove (GST_BIN (_pipeline), sink->mux); + gst_bin_remove (GST_BIN (_pipeline), sink->filesink); + + _pipeline2 = gst_pipeline_new("pipe2"); + + gst_bin_add_many(GST_BIN(_pipeline2), sink->queue, sink->mux, sink->filesink, NULL); + gst_element_link_many(sink->queue, sink->mux, sink->filesink, NULL); + + GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(_pipeline2)); + gst_bus_add_signal_watch(bus); + g_signal_connect(bus, "message::eos", G_CALLBACK(_eosCB), NULL); + + if(gst_element_set_state(_pipeline2, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qDebug() << "problem starting pipeline2"; + } + + GstPad* eosInjectPad = gst_element_get_static_pad(sink->queue, "sink"); + gst_pad_send_event(eosInjectPad, gst_event_new_eos()); + gst_object_unref(eosInjectPad); + + return GST_PAD_PROBE_REMOVE; +} + +void VideoReceiver::_startRecording(void) +{ + // exit immediately if we are already recording + if(_pipeline == NULL || _recording) { + return; + } + + sink = g_new0(Sink, 1); + + sink->teepad = gst_element_get_request_pad(tee, "src_%u"); + sink->queue = gst_element_factory_make("queue", NULL); + sink->mux = gst_element_factory_make("matroskamux", NULL); + sink->filesink = gst_element_factory_make("filesink", NULL); + sink->removing = false; + + QString filename = QDir::homePath() + "/" + QDateTime::currentDateTime().toString() + ".mkv"; + g_object_set(G_OBJECT(sink->filesink), "location", qPrintable(filename), NULL); + + gst_object_ref(sink->queue); + gst_object_ref(sink->mux); + gst_object_ref(sink->filesink); + + gst_bin_add_many(GST_BIN(_pipeline), sink->queue, sink->mux, sink->filesink, NULL); + gst_element_link_many(sink->queue, sink->mux, sink->filesink, NULL); + + gst_element_sync_state_with_parent(sink->queue); + gst_element_sync_state_with_parent(sink->mux); + gst_element_sync_state_with_parent(sink->filesink); + + GstPad* sinkpad = gst_element_get_static_pad(sink->queue, "sink"); + gst_pad_link(sink->teepad, sinkpad); + gst_object_unref(sinkpad); + + _recording = true; + emit recordingChanged(); +} + +void VideoReceiver::_stopRecording(void) +{ + // exit immediately if we are not recording + if(_pipeline == NULL || !_recording) { + return; + } + + gst_pad_add_probe(sink->teepad, GST_PAD_PROBE_TYPE_IDLE, _unlinkCB, sink, NULL); + + _recording = false; + emit recordingChanged(); +} VideoReceiver::VideoReceiver(QObject* parent) : QObject(parent) #if defined(QGC_GST_STREAMING) - , _pipeline(NULL) + , _recording(false) , _videoSink(NULL) , _socket(NULL) , _serverPresent(false) @@ -36,11 +161,12 @@ VideoReceiver::VideoReceiver(QObject* parent) VideoReceiver::~VideoReceiver() { #if defined(QGC_GST_STREAMING) - stop(); - setVideoSink(NULL); - if(_socket) { - delete _socket; - } +// stop(); +// setVideoSink(NULL); +// if(_socket) { +// delete _socket; +// } + EOS(); #endif } @@ -149,6 +275,15 @@ void VideoReceiver::start() GstElement* demux = NULL; GstElement* parser = NULL; GstElement* decoder = NULL; + GstElement* queue1 = NULL; + + // Pads to link queues and tee + GstPad* teeSrc1 = NULL; // tee source pad 1 + GstPad* q1Sink = NULL; // queue1 sink pad + + // /queue1---decoder---_videosink + //datasource---demux---parser---tee + // \queue2---matroskamux---filesink do { if ((_pipeline = gst_pipeline_new("receiver")) == NULL) { @@ -196,21 +331,51 @@ void VideoReceiver::start() break; } - gst_bin_add_many(GST_BIN(_pipeline), dataSource, demux, parser, decoder, _videoSink, NULL); + if((tee = gst_element_factory_make("tee", "stream-file-tee")) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('tee')"; + break; + } - gboolean res = FALSE; + if((queue1 = gst_element_factory_make("queue", NULL)) == NULL) { + qCritical() << "VideoReceiver::start() failed. Error with gst_element_factory_make('queue1')"; + break; + } - if(isUdp) { - res = gst_element_link_many(dataSource, demux, parser, decoder, _videoSink, NULL); - } else { - res = gst_element_link_many(demux, parser, decoder, _videoSink, NULL); + gst_bin_add_many(GST_BIN(_pipeline), dataSource, demux, parser, tee, queue1, decoder, _videoSink, NULL); + +// if(isUdp) { +// res = gst_element_link_many(dataSource, demux, parser, decoder, tee, _videoSink, NULL); +// } else { +// res = gst_element_link_many(demux, parser, decoder, tee, _videoSink, NULL); +// } + + // Link the pipeline in front of the tee + if(!gst_element_link_many(dataSource, demux, parser, tee, NULL)) { + qCritical() << "Unable to link datasource and tee."; + break; } - if (!res) { - qCritical() << "VideoReceiver::start() failed. Error with gst_element_link_many()"; + // Link the videostream to queue1 + if(!gst_element_link_many(queue1, decoder, _videoSink, NULL)) { + qCritical() << "Unable to link queue1 and videosink."; break; } + // Link the queues to the tee + teeSrc1 = gst_element_get_request_pad(tee, "src_%u"); + q1Sink = gst_element_get_static_pad(queue1, "sink"); + + // Link the tee to queue1 + if (gst_pad_link(teeSrc1, q1Sink) != GST_PAD_LINK_OK ){ + qCritical() << "Tee for queue1 could not be linked.\n"; + break; + } + + gst_object_unref(teeSrc1); + gst_object_unref(q1Sink); + + teeSrc1 = q1Sink = NULL; + queue1 = NULL; dataSource = demux = parser = decoder = NULL; GstBus* bus = NULL; @@ -253,18 +418,35 @@ void VideoReceiver::start() dataSource = NULL; } + if (tee != NULL) { + gst_object_unref(tee); + dataSource = NULL; + } + + if (queue1 != NULL) { + gst_object_unref(queue1); + dataSource = NULL; + } + if (_pipeline != NULL) { gst_object_unref(_pipeline); _pipeline = NULL; } } + + qDebug() << "Video Receiver started."; #endif } +void VideoReceiver::EOS() { + gst_element_send_event(_pipeline, gst_event_new_eos()); +} + void VideoReceiver::stop() { #if defined(QGC_GST_STREAMING) if (_pipeline != NULL) { + qCritical() << "stopping pipeline"; gst_element_set_state(_pipeline, GST_STATE_NULL); gst_object_unref(_pipeline); _pipeline = NULL; @@ -282,9 +464,12 @@ void VideoReceiver::setUri(const QString & uri) #if defined(QGC_GST_STREAMING) void VideoReceiver::_onBusMessage(GstMessage* msg) { + //qDebug() << "Got bus message"; + switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: - stop(); + qDebug() << "Got EOS"; + //stop(); break; case GST_MESSAGE_ERROR: do { diff --git a/src/VideoStreaming/VideoReceiver.h b/src/VideoStreaming/VideoReceiver.h index 6069a99a1..4477b5109 100644 --- a/src/VideoStreaming/VideoReceiver.h +++ b/src/VideoStreaming/VideoReceiver.h @@ -29,6 +29,8 @@ class VideoReceiver : public QObject { Q_OBJECT public: + Q_PROPERTY(bool recording READ recording NOTIFY recordingChanged) + explicit VideoReceiver(QObject* parent = 0); ~VideoReceiver(); @@ -36,10 +38,18 @@ public: void setVideoSink(GstElement* sink); #endif + bool recording() { return _recording; } + +signals: + void recordingChanged(); + public slots: void start (); + void EOS (); void stop (); void setUri (const QString& uri); + void _stopRecording(); + void _startRecording(); private slots: #if defined(QGC_GST_STREAMING) @@ -49,16 +59,34 @@ private slots: #endif private: - #if defined(QGC_GST_STREAMING) - void _onBusMessage(GstMessage* message); - static gboolean _onBusMessage(GstBus* bus, GstMessage* msg, gpointer data); + typedef struct + { + GstPad* teepad; + GstElement* queue; + GstElement* mux; + GstElement* filesink; + gboolean removing; + } Sink; + + static Sink* sink; + + void _onBusMessage(GstMessage* message); + static gboolean _onBusMessage(GstBus* bus, GstMessage* msg, gpointer user_data); + static gboolean _eosCB(GstBus* bus, GstMessage* message, gpointer user_data); + static GstPadProbeReturn _unlinkCB(GstPad* pad, GstPadProbeInfo* info, gpointer user_data); + + bool _recording; + static GstElement* tee; + #endif QString _uri; #if defined(QGC_GST_STREAMING) - GstElement* _pipeline; + static GstElement* _pipeline; + static GstElement* _pipeline2; + GstElement* _videoSink; #endif diff --git a/src/ui/toolbar/MainToolBarIndicators.qml b/src/ui/toolbar/MainToolBarIndicators.qml index 73c339325..d2bfaa8a2 100644 --- a/src/ui/toolbar/MainToolBarIndicators.qml +++ b/src/ui/toolbar/MainToolBarIndicators.qml @@ -489,6 +489,27 @@ Item { } } + //------------------------------------------------------------------------- + //-- Video Recording + Item { + anchors.top: parent.top + anchors.bottom: parent.bottom + width: height + + Rectangle { + anchors.top: parent.top + anchors.bottom: parent.bottom + width: height + radius: QGroundControl.videoManager.videoReceiver.recording ? 0 : height + color: colorRed + } + + MouseArea { + anchors.fill: parent + onClicked: QGroundControl.videoManager.videoReceiver.recording? QGroundControl.videoManager.videoReceiver._stopRecording() : QGroundControl.videoManager.videoReceiver._startRecording() + } + } + //------------------------------------------------------------------------- //-- Mode Selector QGCLabel { -- 2.22.0