// 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(); } } }