|
|
|
@ -17,6 +17,7 @@
@@ -17,6 +17,7 @@
|
|
|
|
|
#include "VideoReceiver.h" |
|
|
|
|
#include "SettingsManager.h" |
|
|
|
|
#include "QGCApplication.h" |
|
|
|
|
#include "VideoManager.h" |
|
|
|
|
|
|
|
|
|
#include <QDebug> |
|
|
|
|
#include <QUrl> |
|
|
|
@ -26,6 +27,22 @@
@@ -26,6 +27,22 @@
|
|
|
|
|
|
|
|
|
|
QGC_LOGGING_CATEGORY(VideoReceiverLog, "VideoReceiverLog") |
|
|
|
|
|
|
|
|
|
static const char* kVideoExtensions[] = |
|
|
|
|
{ |
|
|
|
|
"mkv", |
|
|
|
|
"mov", |
|
|
|
|
"mp4" |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static const char* kVideoMuxes[] = |
|
|
|
|
{ |
|
|
|
|
"matroskamux", |
|
|
|
|
"qtmux", |
|
|
|
|
"mp4mux" |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#define NUM_MUXES (sizeof(kVideoMuxes) / sizeof(char*)) |
|
|
|
|
|
|
|
|
|
VideoReceiver::VideoReceiver(QObject* parent) |
|
|
|
|
: QObject(parent) |
|
|
|
|
#if defined(QGC_GST_STREAMING) |
|
|
|
@ -444,6 +461,39 @@ gboolean VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer dat
@@ -444,6 +461,39 @@ gboolean VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer dat
|
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
void VideoReceiver::_cleanupOldVideos() |
|
|
|
|
{ |
|
|
|
|
QString savePath = qgcApp()->toolbox()->settingsManager()->videoSettings()->videoSavePath()->rawValue().toString(); |
|
|
|
|
QDir videoDir = QDir(savePath); |
|
|
|
|
videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable); |
|
|
|
|
videoDir.setSorting(QDir::Time); |
|
|
|
|
//-- All the movie extensions we support
|
|
|
|
|
QStringList nameFilters; |
|
|
|
|
for(uint32_t i = 0; i < NUM_MUXES; i++) { |
|
|
|
|
nameFilters << QString("*.") + QString(kVideoExtensions[i]); |
|
|
|
|
} |
|
|
|
|
videoDir.setNameFilters(nameFilters); |
|
|
|
|
//-- get the list of videos stored
|
|
|
|
|
QFileInfoList vidList = videoDir.entryInfoList(); |
|
|
|
|
if(!vidList.isEmpty()) { |
|
|
|
|
uint64_t total = 0; |
|
|
|
|
//-- Settings are stored using MB
|
|
|
|
|
uint64_t maxSize = (qgcApp()->toolbox()->settingsManager()->videoSettings()->maxVideoSize()->rawValue().toUInt() * 1024 * 1024); |
|
|
|
|
//-- Compute total used storage
|
|
|
|
|
for(int i = 0; i < vidList.size(); i++) { |
|
|
|
|
total += vidList[i].size(); |
|
|
|
|
} |
|
|
|
|
//-- Remove old movies until max size is satisfied.
|
|
|
|
|
while(total >= maxSize && !vidList.isEmpty()) { |
|
|
|
|
total -= vidList.last().size(); |
|
|
|
|
qCDebug(VideoReceiverLog) << "Removing old video file:" << vidList.last().filePath(); |
|
|
|
|
QFile file (vidList.last().filePath()); |
|
|
|
|
file.remove(); |
|
|
|
|
vidList.removeLast(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// When we finish our pipeline will look like this:
|
|
|
|
|
//
|
|
|
|
|
// +-->queue-->decoder-->_videosink
|
|
|
|
@ -457,7 +507,7 @@ gboolean VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer dat
@@ -457,7 +507,7 @@ gboolean VideoReceiver::_onBusMessage(GstBus* bus, GstMessage* msg, gpointer dat
|
|
|
|
|
// +--------------------------------------+
|
|
|
|
|
void VideoReceiver::startRecording(void) |
|
|
|
|
{ |
|
|
|
|
#if defined(QGC_GST_STREAMING) && defined(QGC_ENABLE_VIDEORECORDING) |
|
|
|
|
#if defined(QGC_GST_STREAMING) |
|
|
|
|
|
|
|
|
|
qCDebug(VideoReceiverLog) << "startRecording()"; |
|
|
|
|
// exit immediately if we are already recording
|
|
|
|
@ -468,36 +518,48 @@ void VideoReceiver::startRecording(void)
@@ -468,36 +518,48 @@ void VideoReceiver::startRecording(void)
|
|
|
|
|
|
|
|
|
|
QString savePath = qgcApp()->toolbox()->settingsManager()->videoSettings()->videoSavePath()->rawValue().toString(); |
|
|
|
|
if(savePath.isEmpty()) { |
|
|
|
|
qgcApp()->showMessage("Unabled to record video. Video save path must be specified in Settings."); |
|
|
|
|
qgcApp()->showMessage(tr("Unabled to record video. Video save path must be specified in Settings.")); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint32_t muxIdx = qgcApp()->toolbox()->settingsManager()->videoSettings()->recordingFormat()->rawValue().toUInt(); |
|
|
|
|
if(muxIdx >= NUM_MUXES) { |
|
|
|
|
qgcApp()->showMessage(tr("Invalid video format defined.")); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//-- Disk usage maintenance
|
|
|
|
|
_cleanupOldVideos(); |
|
|
|
|
|
|
|
|
|
_sink = new Sink(); |
|
|
|
|
_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->parse = gst_element_factory_make("h264parse", NULL); |
|
|
|
|
_sink->mux = gst_element_factory_make(kVideoMuxes[muxIdx], NULL); |
|
|
|
|
_sink->filesink = gst_element_factory_make("filesink", NULL); |
|
|
|
|
_sink->removing = false; |
|
|
|
|
|
|
|
|
|
if(!_sink->teepad || !_sink->queue || !_sink->mux || !_sink->filesink) { |
|
|
|
|
if(!_sink->teepad || !_sink->queue || !_sink->mux || !_sink->filesink || !_sink->parse) { |
|
|
|
|
qCritical() << "VideoReceiver::startRecording() failed to make _sink elements"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QString videoFile; |
|
|
|
|
videoFile = savePath + "/QGC-" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss") + ".mkv"; |
|
|
|
|
videoFile = savePath + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss") + "." + kVideoExtensions[muxIdx]; |
|
|
|
|
|
|
|
|
|
g_object_set(G_OBJECT(_sink->filesink), "location", qPrintable(videoFile), NULL); |
|
|
|
|
qCDebug(VideoReceiverLog) << "New video file:" << videoFile; |
|
|
|
|
|
|
|
|
|
gst_object_ref(_sink->queue); |
|
|
|
|
gst_object_ref(_sink->parse); |
|
|
|
|
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_bin_add_many(GST_BIN(_pipeline), _sink->queue, _sink->parse, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
gst_element_link_many(_sink->queue, _sink->parse, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
|
|
|
|
|
gst_element_sync_state_with_parent(_sink->queue); |
|
|
|
|
gst_element_sync_state_with_parent(_sink->parse); |
|
|
|
|
gst_element_sync_state_with_parent(_sink->mux); |
|
|
|
|
gst_element_sync_state_with_parent(_sink->filesink); |
|
|
|
|
|
|
|
|
@ -513,7 +575,7 @@ void VideoReceiver::startRecording(void)
@@ -513,7 +575,7 @@ void VideoReceiver::startRecording(void)
|
|
|
|
|
|
|
|
|
|
void VideoReceiver::stopRecording(void) |
|
|
|
|
{ |
|
|
|
|
#if defined(QGC_GST_STREAMING) && defined(QGC_ENABLE_VIDEORECORDING) |
|
|
|
|
#if defined(QGC_GST_STREAMING) |
|
|
|
|
qCDebug(VideoReceiverLog) << "stopRecording()"; |
|
|
|
|
// exit immediately if we are not recording
|
|
|
|
|
if(_pipeline == NULL || !_recording) { |
|
|
|
@ -534,6 +596,7 @@ void VideoReceiver::stopRecording(void)
@@ -534,6 +596,7 @@ void VideoReceiver::stopRecording(void)
|
|
|
|
|
void VideoReceiver::_shutdownRecordingBranch() |
|
|
|
|
{ |
|
|
|
|
gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->queue); |
|
|
|
|
gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->parse); |
|
|
|
|
gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->mux); |
|
|
|
|
gst_bin_remove(GST_BIN(_pipelineStopRec), _sink->filesink); |
|
|
|
|
|
|
|
|
@ -541,11 +604,13 @@ void VideoReceiver::_shutdownRecordingBranch()
@@ -541,11 +604,13 @@ void VideoReceiver::_shutdownRecordingBranch()
|
|
|
|
|
gst_object_unref(_pipelineStopRec); |
|
|
|
|
_pipelineStopRec = NULL; |
|
|
|
|
|
|
|
|
|
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_element_set_state(_sink->filesink, GST_STATE_NULL); |
|
|
|
|
gst_element_set_state(_sink->parse, 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->parse); |
|
|
|
|
gst_object_unref(_sink->mux); |
|
|
|
|
gst_object_unref(_sink->filesink); |
|
|
|
|
|
|
|
|
@ -568,7 +633,7 @@ void VideoReceiver::_detachRecordingBranch(GstPadProbeInfo* info)
@@ -568,7 +633,7 @@ void VideoReceiver::_detachRecordingBranch(GstPadProbeInfo* info)
|
|
|
|
|
Q_UNUSED(info) |
|
|
|
|
|
|
|
|
|
// Also unlinks and unrefs
|
|
|
|
|
gst_bin_remove_many(GST_BIN(_pipeline), _sink->queue, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
gst_bin_remove_many(GST_BIN(_pipeline), _sink->queue, _sink->parse, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
|
|
|
|
|
// Give tee its pad back
|
|
|
|
|
gst_element_release_request_pad(_tee, _sink->teepad); |
|
|
|
@ -578,8 +643,8 @@ void VideoReceiver::_detachRecordingBranch(GstPadProbeInfo* info)
@@ -578,8 +643,8 @@ void VideoReceiver::_detachRecordingBranch(GstPadProbeInfo* info)
|
|
|
|
|
_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); |
|
|
|
|
gst_bin_add_many(GST_BIN(_pipelineStopRec), _sink->queue, _sink->parse, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
gst_element_link_many(_sink->queue, _sink->parse, _sink->mux, _sink->filesink, NULL); |
|
|
|
|
|
|
|
|
|
// Add handler for EOS event
|
|
|
|
|
GstBus* bus = gst_pipeline_get_bus(GST_PIPELINE(_pipelineStopRec)); |
|
|
|
|