- removed -r command line option and renamed it to -s. then added -l as what -s used to be. - added detect_uri option to camera parameters so stream snapshots can now run a secondary stream that differs from the recording stream. also added sync logic so the recording loop and detection loop will run synchronously while still running in seperate threads. - live_secs renamed to live_mins. the amount of live footage to buffer in buffer_path is now based on minutes instead of seconds. - added live_clip_secs parameter so the amount of seconds each hls video clip should have is now adjustable. - remove max_event_secs. events are now transfered to rec_path based on minute increments. - added sec_per_image to make the amount of seconds between stream snapshots pulled from detect_uri adjustable. - added img_ext so the image snapshot format from detect_uri and then ultimately to img_comp_cmd can now be configured instead of being hardcoded to bmp. - added gray_image as a boolean parameter so the snapshots from detect_uri can be pulled in grayscale format or remain in color if desired. - removed img_scale since this parameter was not doing anything. overview: use of QFileSystemWatcher on the detection loop has been eliminated and thus eliminated use of functions such as listFacingFiles(), backwardFacingFiles() and forwardFacingFiles(). Instead, the detection and recording loops will now run synchronously and use predictable up-to-the-minute file naming scheme. client v1.1: - the build script will now include all imageformat plugins from the QT lib directory, adding support for jpg, svg, ico and gif image formats. - switched to 2 number versioning. - this client app will now support the server's new up-to-the-minute directory structure for live and recorded footage. - the version number is now be displayed on the main interface.
167 lines
4.6 KiB
C++
167 lines
4.6 KiB
C++
// This file is part of JustMotion.
|
|
|
|
// JustMotion is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// JustMotion is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
#include "record_loop.h"
|
|
|
|
RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent)
|
|
{
|
|
checkTimer = 0;
|
|
shared = sharedRes;
|
|
|
|
connect(thr, &QThread::started, this, &RecordLoop::init);
|
|
connect(thr, &QThread::finished, this, &RecordLoop::deleteLater);
|
|
|
|
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
|
|
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
|
|
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
|
|
connect(this, &RecordLoop::finished, this, &RecordLoop::restart);
|
|
|
|
moveToThread(thr);
|
|
}
|
|
|
|
RecordLoop::~RecordLoop()
|
|
{
|
|
disconnect(this, &RecordLoop::finished, this, &RecordLoop::restart);
|
|
|
|
terminate();
|
|
waitForFinished();
|
|
}
|
|
|
|
void RecordLoop::init()
|
|
{
|
|
checkTimer = new QTimer(this);
|
|
mkdirTimer = new QTimer(this);
|
|
sync = false;
|
|
|
|
connect(checkTimer, &QTimer::timeout, this, &RecordLoop::restart);
|
|
connect(mkdirTimer, &QTimer::timeout, this, &RecordLoop::mkdirs);
|
|
|
|
connect(this, &RecordLoop::finished, mkdirTimer, &QTimer::stop);
|
|
connect(this, &RecordLoop::finished, checkTimer, &QTimer::stop);
|
|
|
|
mkdirTimer->setInterval(60000);
|
|
checkTimer->setInterval(60000);
|
|
// stall timeout. ffmpeg has a tendency to just 'stop.' meaning it would just stop
|
|
// reording at random times without stop signals or even error messages. this timer,
|
|
// monitors for any text output from ffmpeg. if no updates in 60000 msecs, it will
|
|
// automatically restart ffmpeg.
|
|
|
|
setupBuffDir(shared->buffPath);
|
|
restart();
|
|
}
|
|
|
|
void RecordLoop::mkdirs()
|
|
{
|
|
auto timeStamp = QDateTime::currentDateTime();
|
|
auto path1 = shared->buffPath + "/vid/" + timeStamp.toString(DATETIME_FMT);
|
|
|
|
timeStamp = timeStamp.addSecs(60);
|
|
|
|
auto path2 = shared->buffPath + "/vid/" + timeStamp.toString(DATETIME_FMT);
|
|
|
|
if (!QFileInfo(path1).exists()) QDir().mkpath(path1);
|
|
if (!QFileInfo(path2).exists()) QDir().mkpath(path2);
|
|
}
|
|
|
|
QString RecordLoop::camCmdFromConf()
|
|
{
|
|
auto ret = "ffmpeg -hide_banner -y -i '" + shared->recordUri + "' -strftime 1 -strftime_mkdir 1 ";
|
|
|
|
if (shared->recordUri.contains("rtsp"))
|
|
{
|
|
ret += "-rtsp_transport udp ";
|
|
}
|
|
|
|
if (shared->vidCodec != "copy")
|
|
{
|
|
ret += "-vf fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale + " ";
|
|
}
|
|
|
|
auto maxClips = (60 / shared->liveClipSecs) * shared->liveMins;
|
|
|
|
ret += "-vcodec " + shared->vidCodec + " ";
|
|
ret += "-acodec " + shared->audCodec + " ";
|
|
ret += "-hls_time " + QString::number(shared->liveClipSecs) + " -hls_list_size " + QString::number(maxClips) + " ";
|
|
ret += "-hls_flags delete_segments -hls_segment_filename vid/" + QString(STRFTIME_FMT) + "/%S" + shared->streamExt + " ";
|
|
ret += "pls.m3u8";
|
|
|
|
qInfo() << ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
QString RecordLoop::statusLine()
|
|
{
|
|
if (!sync)
|
|
{
|
|
return "WAIT ";
|
|
}
|
|
else
|
|
{
|
|
return getStatLineFromProc(this);
|
|
}
|
|
}
|
|
|
|
void RecordLoop::synced()
|
|
{
|
|
sync = true;
|
|
|
|
restart();
|
|
}
|
|
|
|
void RecordLoop::resetTime()
|
|
{
|
|
// reset the stall timer to prevent it from timing out when ffmpeg doesn't
|
|
// need to be restarted.
|
|
|
|
checkTimer->start();
|
|
}
|
|
|
|
void RecordLoop::restart()
|
|
{
|
|
auto sec = QTime::currentTime().second();
|
|
|
|
if (state() == QProcess::Running)
|
|
{
|
|
terminate();
|
|
}
|
|
else if (!sync && (sec != 0))
|
|
{
|
|
// start recording when the seconds mark hit zero in real life.
|
|
|
|
QTimer::singleShot((60 - sec) * 1000, this, &RecordLoop::synced);
|
|
}
|
|
else
|
|
{
|
|
auto cmdLine = camCmdFromConf();
|
|
auto args = parseArgs(cmdLine.toUtf8(), -1);
|
|
|
|
//qInfo() << "start recording command: " << cmdLine;
|
|
|
|
if (args.isEmpty())
|
|
{
|
|
qCritical() << "err: couldn't parse a program name";
|
|
}
|
|
else
|
|
{
|
|
setWorkingDirectory(shared->buffPath);
|
|
setProgram(args[0]);
|
|
setArguments(args.mid(1));
|
|
mkdirs();
|
|
|
|
mkdirTimer->start();
|
|
|
|
start();
|
|
}
|
|
}
|
|
}
|