|
|
|
@ -23,178 +23,6 @@
@@ -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
@@ -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 |
|
|
|
|