diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoStreaming/VideoReceiver.cc index 9cba31820f91547e2cc6d985cd5fbf9f19886b20..927dbe05e1076e88027c33bd68e0741f2f0f449b 100644 --- a/src/VideoStreaming/VideoReceiver.cc +++ b/src/VideoStreaming/VideoReceiver.cc @@ -23,178 +23,6 @@ QGC_LOGGING_CATEGORY(VideoReceiverLog, "VideoReceiverLog") -// -EOS has appeared on the bus of the temporary pipeline -// -At this point all of the recoring elements have been flushed, and the video file has been finalized -// -Now we can remove the temporary pipeline and its elements -#if defined(QGC_GST_STREAMING) -void VideoReceiver::_eosCB(GstMessage* message) -{ - Q_UNUSED(message) - - gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->queue); - gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->mux); - gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->filesink); - - gst_element_set_state(_pipelineStopRec, GST_STATE_NULL); - gst_object_unref(_pipelineStopRec); - - 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; - - _recording = false; - emit recordingChanged(); - qCDebug(VideoReceiverLog) << "Recording Stopped"; -} -#endif - -// -Unlink the recording branch from the tee in the main pipeline -// -Create a second temporary pipeline, and place the recording branch elements into that pipeline -// -Setup watch and handler for EOS event on the temporary pipeline's bus -// -Send an EOS event at the beginning of that pipeline and set up a callback for -#if defined(QGC_GST_STREAMING) -void VideoReceiver::_unlinkCB(GstPadProbeInfo* info) -{ - Q_UNUSED(info) - - // Also unlinks and unrefs - gst_bin_remove_many(GST_BIN(_pipeline), _sink->queue, _sink->mux, _sink->filesink, NULL); - - // Give tee its pad back - gst_element_release_request_pad(_tee, _sink->teepad); - gst_object_unref(_sink->teepad); - - // Create temporary pipeline - _pipelineStopRec = gst_pipeline_new("pipeStopRec"); - - // Put our elements from the recording branch into the temporary pipeline - gst_bin_add_many(GST_BIN(_pipelineStopRec), _sink->queue, _sink->mux, _sink->filesink, NULL); - gst_element_link_many(_sink->queue, _sink->mux, _sink->filesink, NULL); - - // Add watch for EOS event - GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(_pipelineStopRec)); - gst_bus_add_signal_watch(bus); - g_signal_connect(bus, "message::eos", G_CALLBACK(_eosCallBack), this); - gst_object_unref(bus); - - if(gst_element_set_state(_pipelineStopRec, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { - qCDebug(VideoReceiverLog) << "problem starting _pipelineStopRec"; - } - - // Send EOS at the beginning of the pipeline - GstPad* sinkpad = gst_element_get_static_pad(_sink->queue, "sink"); - gst_pad_send_event(sinkpad, gst_event_new_eos()); - gst_object_unref(sinkpad); - qCDebug(VideoReceiverLog) << "Recording branch unlinked"; -} -#endif - -#if defined(QGC_GST_STREAMING) -gboolean VideoReceiver::_eosCallBack(GstBus* bus, GstMessage* message, gpointer user_data) -{ - Q_UNUSED(bus) - Q_ASSERT(message != NULL && user_data != NULL); - VideoReceiver* pThis = (VideoReceiver*)user_data; - pThis->_eosCB(message); - return FALSE; -} -#endif - -#if defined(QGC_GST_STREAMING) -GstPadProbeReturn VideoReceiver::_unlinkCallBack(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) -{ - Q_UNUSED(pad); - Q_ASSERT(info != NULL && user_data != NULL); - VideoReceiver* pThis = (VideoReceiver*)user_data; - // We will only execute once - if(!g_atomic_int_compare_and_exchange(&pThis->_sink->removing, FALSE, TRUE)) - return GST_PAD_PROBE_REMOVE; - pThis->_unlinkCB(info); - return GST_PAD_PROBE_REMOVE; -} - -#endif - -// When we finish our pipeline will look like this: -// -// +-->queue-->decoder-->_videosink -// | -// datasource-->demux-->parser-->tee -// | -// | +--------------_sink-------------------+ -// | | | -// we are adding these elements-> +->teepad-->queue-->matroskamux-->_filesink | -// | | -// +--------------------------------------+ -void VideoReceiver::startRecording(void) -{ -#if defined(QGC_GST_STREAMING) - qCDebug(VideoReceiverLog) << "startRecording()"; - // exit immediately if we are already recording - if(_pipeline == NULL || _recording) { - qCDebug(VideoReceiverLog) << "Already 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; - if(QSysInfo::WindowsVersion != QSysInfo::WV_None) { - fileName = _path + "\\QGC-" + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss") + ".mkv"; - } else { - fileName = _path + "/QGC-" + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss") + ".mkv"; - } - - g_object_set(G_OBJECT(_sink->filesink), "location", qPrintable(fileName), NULL); - qCDebug(VideoReceiverLog) << "New video file:" << fileName; - - 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(); - qCDebug(VideoReceiverLog) << "Recording started"; -#endif -} - -void VideoReceiver::stopRecording(void) -{ -#if defined(QGC_GST_STREAMING) - qCDebug(VideoReceiverLog) << "stopRecording()"; - // exit immediately if we are not recording - if(_pipeline == NULL || !_recording) { - qCDebug(VideoReceiverLog) << "Not recording!"; - return; - } - // Wait for data block before unlinking - gst_pad_add_probe(_sink->teepad, GST_PAD_PROBE_TYPE_IDLE, _unlinkCallBack, this, NULL); -#endif -} - VideoReceiver::VideoReceiver(QObject* parent) : QObject(parent) , _running(false) @@ -559,3 +387,183 @@ gboolean VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer dat return TRUE; } #endif + +// When we finish our pipeline will look like this: +// +// +-->queue-->decoder-->_videosink +// | +// datasource-->demux-->parser-->tee +// | +// | +--------------_sink-------------------+ +// | | | +// we are adding these elements-> +->teepad-->queue-->matroskamux-->_filesink | +// | | +// +--------------------------------------+ +void VideoReceiver::startRecording(void) +{ +#if defined(QGC_GST_STREAMING) + qCDebug(VideoReceiverLog) << "startRecording()"; + // exit immediately if we are already recording + if(_pipeline == NULL || _recording) { + qCDebug(VideoReceiverLog) << "Already 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; + + if(!_sink->teepad || !_sink->queue || !_sink->mux || !_sink->filesink) { + qCritical() << "VideoReceiver::startRecording() failed to make _sink elements"; + return; + } + + QString fileName; + if(QSysInfo::WindowsVersion != QSysInfo::WV_None) { + fileName = _path + "\\QGC-" + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss") + ".mkv"; + } else { + fileName = _path + "/QGC-" + QDateTime::currentDateTime().toString("yyyy-MM-dd-hh:mm:ss") + ".mkv"; + } + + g_object_set(G_OBJECT(_sink->filesink), "location", qPrintable(fileName), NULL); + qCDebug(VideoReceiverLog) << "New video file:" << fileName; + + 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(); + qCDebug(VideoReceiverLog) << "Recording started"; +#endif +} + +void VideoReceiver::stopRecording(void) +{ +#if defined(QGC_GST_STREAMING) + qCDebug(VideoReceiverLog) << "stopRecording()"; + // exit immediately if we are not recording + if(_pipeline == NULL || !_recording) { + qCDebug(VideoReceiverLog) << "Not recording!"; + return; + } + // Wait for data block before unlinking + gst_pad_add_probe(_sink->teepad, GST_PAD_PROBE_TYPE_IDLE, _unlinkCallBack, this, NULL); +#endif +} + +// This is only installed on the transient _pipelineStopRec in order +// to finalize a video file. It is not used for the main _pipeline. +// -EOS has appeared on the bus of the temporary pipeline +// -At this point all of the recoring elements have been flushed, and the video file has been finalized +// -Now we can remove the temporary pipeline and its elements +#if defined(QGC_GST_STREAMING) +void VideoReceiver::_eosCB(GstMessage* message) +{ + Q_UNUSED(message) + + gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->queue); + gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->mux); + gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->filesink); + + gst_element_set_state(_pipelineStopRec, GST_STATE_NULL); + gst_object_unref(_pipelineStopRec); + + 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; + + _recording = false; + emit recordingChanged(); + qCDebug(VideoReceiverLog) << "Recording Stopped"; +} +#endif + +// -Unlink the recording branch from the tee in the main _pipeline +// -Create a second temporary pipeline, and place the recording branch elements into that pipeline +// -Setup watch and handler for EOS event on the temporary pipeline's bus +// -Send an EOS event at the beginning of that pipeline +#if defined(QGC_GST_STREAMING) +void VideoReceiver::_unlinkCB(GstPadProbeInfo* info) +{ + Q_UNUSED(info) + + // Also unlinks and unrefs + gst_bin_remove_many(GST_BIN(_pipeline), _sink->queue, _sink->mux, _sink->filesink, NULL); + + // Give tee its pad back + gst_element_release_request_pad(_tee, _sink->teepad); + gst_object_unref(_sink->teepad); + + // Create temporary pipeline + _pipelineStopRec = gst_pipeline_new("pipeStopRec"); + + // Put our elements from the recording branch into the temporary pipeline + gst_bin_add_many(GST_BIN(_pipelineStopRec), _sink->queue, _sink->mux, _sink->filesink, NULL); + gst_element_link_many(_sink->queue, _sink->mux, _sink->filesink, NULL); + + // Add watch for EOS event + GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(_pipelineStopRec)); + gst_bus_add_signal_watch(bus); + g_signal_connect(bus, "message::eos", G_CALLBACK(_eosCallBack), this); + gst_object_unref(bus); + + if(gst_element_set_state(_pipelineStopRec, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qCDebug(VideoReceiverLog) << "problem starting _pipelineStopRec"; + } + + // Send EOS at the beginning of the pipeline + GstPad* sinkpad = gst_element_get_static_pad(_sink->queue, "sink"); + gst_pad_send_event(sinkpad, gst_event_new_eos()); + gst_object_unref(sinkpad); + qCDebug(VideoReceiverLog) << "Recording branch unlinked"; +} +#endif + +// This is only installed on the transient _pipelineStopRec in order +// to finalize a video file. It is not used for the main _pipeline. +#if defined(QGC_GST_STREAMING) +gboolean VideoReceiver::_eosCallBack(GstBus* bus, GstMessage* message, gpointer user_data) +{ + Q_UNUSED(bus) + Q_ASSERT(message != NULL && user_data != NULL); + VideoReceiver* pThis = (VideoReceiver*)user_data; + pThis->_eosCB(message); + return FALSE; +} +#endif + +#if defined(QGC_GST_STREAMING) +GstPadProbeReturn VideoReceiver::_unlinkCallBack(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) +{ + Q_UNUSED(pad); + Q_ASSERT(info != NULL && user_data != NULL); + VideoReceiver* pThis = (VideoReceiver*)user_data; + // We will only execute once + if(!g_atomic_int_compare_and_exchange(&pThis->_sink->removing, FALSE, TRUE)) + return GST_PAD_PROBE_REMOVE; + pThis->_unlinkCB(info); + return GST_PAD_PROBE_REMOVE; +} +#endif diff --git a/src/VideoStreaming/VideoReceiver.h b/src/VideoStreaming/VideoReceiver.h index 082625ffd01aa001ae745d58eb0a9ca9b68aff03..8de7bb87389b07a0d42dfe4815dbb300027bb415 100644 --- a/src/VideoStreaming/VideoReceiver.h +++ b/src/VideoStreaming/VideoReceiver.h @@ -69,7 +69,6 @@ private slots: private: #if defined(QGC_GST_STREAMING) - typedef struct { GstPad* teepad; @@ -93,7 +92,6 @@ private: static gboolean _onBusMessage(GstBus* bus, GstMessage* message, gpointer user_data); static gboolean _eosCallBack(GstBus* bus, GstMessage* message, gpointer user_data); static GstPadProbeReturn _unlinkCallBack(GstPad* pad, GstPadProbeInfo* info, gpointer user_data); - #endif QString _uri;