diff --git a/src/camera.cpp b/src/camera.cpp index a305e25..c5a1ed8 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -20,15 +20,12 @@ int Camera::start(const QStringList &args) { auto thr1 = new QThread(nullptr); auto thr2 = new QThread(nullptr); - auto thr3 = new QThread(nullptr); - new Upkeep(&shared, thr1, nullptr); - new EventLoop(&shared, thr2, nullptr); - new DetectLoop(&shared, thr3, nullptr); - + new EventLoop(&shared, thr1, nullptr); + new DetectLoop(&shared, thr2, nullptr); + thr1->start(); thr2->start(); - thr3->start(); } return shared.retCode; @@ -75,103 +72,22 @@ bool Loop::exec() return shared->retCode == 0; } -Upkeep::Upkeep(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) {} - -bool Upkeep::exec() -{ - enforceMaxEvents(shared); - enforceMaxImages(shared); - enforceMaxVids(shared); - - return Loop::exec(); -} - EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) { heartBeat = 2; - highScore = 0; - cycles = 0; } -bool EventLoop::exec() +bool EventLoop::wrOutVod(const evt_t &event) { - if (!vidList.isEmpty()) - { - vidList.removeDuplicates(); - - if (vidList.size() > 1) - { - QTextStream(stdout) << "attempting write out of event: " << name << Qt::endl; - - if (wrOutVod()) - { - QProcess proc; - QStringList args; - - args << "convert"; - args << imgPath; - args << shared->recPath + "/" + name + shared->thumbExt; - - proc.start("magick", args); - proc.waitForFinished(); - } - } - - cycles = 0; - highScore = 0; - - vidList.clear(); - } - - QList rmIndx; - - for (auto i = 0; i < shared->recList.size(); ++i) - { - auto event = &shared->recList[i]; - - if (highScore < event->score) - { - name = event->timeStamp.toString(DATETIME_FMT); - imgPath = event->imgPath; - highScore = event->score; - } - - if (event->queAge >= (shared->evMaxSecs / heartBeat)) - { - auto maxSecs = shared->evMaxSecs / 2; - // half the maxsecs value to get front-back half secs - - auto backFiles = backwardFacingFiles(shared->buffPath + "/live", shared->streamExt, event->timeStamp, maxSecs); - auto frontFiles = forwardFacingFiles(shared->buffPath + "/live", shared->streamExt, event->timeStamp, maxSecs); - - vidList.append(backFiles + frontFiles); - rmIndx.append(i); - } - else - { - event->queAge += heartBeat; - } - } - - for (auto i : rmIndx) - { - shared->recList.removeAt(i); - } - - return Loop::exec(); -} - -bool EventLoop::wrOutVod() -{ - auto cnt = 0; - auto concat = name + ".tmp"; auto ret = false; + auto cnt = 0; + auto concat = shared->buffPath + "/live/" + event.timeStamp + ".ctmp"; - QFile file(concat); + QFile file(concat, this); file.open(QFile::WriteOnly); - for (auto &&vid : vidList) + for (auto &&vid : event.vidList) { QTextStream(stdout) << "event_src: " << vid << Qt::endl; @@ -189,7 +105,7 @@ bool EventLoop::wrOutVod() if (cnt == 0) { - QTextStream(stderr) << "err: none of the event hls clips exists, canceling write out." << Qt::endl; + QTextStream(stderr) << "err: none of the event hls clips exists, cancelling write out." << Qt::endl; QFile::remove(concat); } @@ -202,29 +118,59 @@ bool EventLoop::wrOutVod() args << "-safe" << "0"; args << "-i" << concat; args << "-c" << "copy"; - args << shared->recPath + "/" + name + shared->recExt; + args << shared->recPath + "/" + event.timeStamp + shared->recExt; - QProcess::execute("ffmpeg", args); + if (QProcess::execute("ffmpeg", args) == 0) + { + ret = true; + } + QFile::remove(concat); } return ret; } +bool EventLoop::exec() +{ + enforceMaxEvents(shared); + enforceMaxImages(shared); + enforceMaxClips(shared); + + if (!shared->recList.isEmpty()) + { + auto event = shared->recList.takeFirst(); + + QTextStream(stdout) << "attempting write out of event: " << event.timeStamp << Qt::endl; + + if (wrOutVod(event)) + { + QStringList args; + + args << "convert"; + args << event.imgPath; + args << shared->recPath + "/" + event.timeStamp + shared->thumbExt; + + QProcess::execute("magick", args); + } + } + + return Loop::exec(); +} + DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) { - seed = 0; - pcTimer = 0; - heartBeat = 2; - delayCycles = 12; // this will be used to delay the - // actual start of DetectLoop by - // 24secs. + pcTimer = 0; + heartBeat = 2; } void DetectLoop::init() { pcTimer = new QTimer(this); - mod = false; + + eventQue.queAge = 0; + eventQue.score = 0; + eventQue.inQue = false; connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak); @@ -244,15 +190,12 @@ void DetectLoop::pcBreak() { QTextStream(stdout) << "---POST_BREAK---" << Qt::endl; - if (mod) + if (eventQue.inQue) { QTextStream(stdout) << "motion detected, skipping the post command." << Qt::endl; } else { - if (delayCycles == 0) delayCycles = 5; - else delayCycles += 5; - QTextStream(stdout) << "no motion detected, running post command: " << shared->postCmd << Qt::endl; auto args = parseArgs(shared->postCmd.toUtf8(), -1); @@ -267,8 +210,6 @@ void DetectLoop::pcBreak() } } } - - mod = false; } float DetectLoop::getFloatFromExe(const QByteArray &line) @@ -288,7 +229,16 @@ float DetectLoop::getFloatFromExe(const QByteArray &line) } } - return strNum.toFloat(); + auto ok = false; + auto res = strNum.toFloat(&ok); + + if (!ok || strNum.isEmpty()) + { + QTextStream(stderr) << "err: the image comp command returned unexpected output and couldn't be converted to float." << Qt::endl; + QTextStream(stderr) << " raw output: " << line << Qt::endl; + } + + return res; } QStringList DetectLoop::buildArgs(const QString &prev, const QString &next) @@ -308,6 +258,7 @@ QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgP { QStringList ret; + ret.append("-hide_banner"); ret.append("-y"); ret.append("-i"); ret.append(vidSrc); @@ -320,88 +271,95 @@ QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgP bool DetectLoop::exec() { - if (delayCycles > 0) + if (eventQue.inQue) { - delayCycles -= 1; + eventQue.queAge += heartBeat; + } - QTextStream(stdout) << "delay: detection cycle skipped. cycles left to be skipped: " << QString::number(delayCycles) << Qt::endl; + auto clips = lsFilesInDir(shared->buffPath + "/live", shared->streamExt); + + if (clips.size() < 2) + { + QTextStream(stdout) << "warning: didn't pick up enough clips files from the video stream. number of files: " << QString::number(clips.size()) << Qt::endl; + QTextStream(stdout) << " will try again on the next loop." << Qt::endl; } else { - auto imgAPath = "img/" + QString::number(seed++) + ".bmp"; - auto imgBPath = "img/" + QString::number(seed++) + ".bmp"; - auto curDT = QDateTime::currentDateTime(); - auto clips = backwardFacingFiles("live", shared->streamExt, curDT, 16); + auto vidAPath = shared->buffPath + "/live/" + clips[clips.size() - 2]; + auto vidBPath = shared->buffPath + "/live/" + clips[clips.size() - 1]; + auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp"; + auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp"; + auto snapArgsA = buildSnapArgs(vidAPath, imgAPath); + auto snapArgsB = buildSnapArgs(vidBPath, imgBPath); + auto compArgs = buildArgs(imgAPath, imgBPath); - if (clips.size() < 2) + if (compArgs.isEmpty()) { - QTextStream(stdout) << "warning: didn't pick up enough clips files from the video stream. number of files: " << QString::number(clips.size()) << Qt::endl; - QTextStream(stdout) << " will try again on the next loop." << Qt::endl; + QTextStream(stderr) << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd << Qt::endl; } else { - auto snapArgsA = buildSnapArgs(clips[clips.size() - 2], imgAPath); - auto snapArgsB = buildSnapArgs(clips[clips.size() - 1], imgAPath); - auto compArgs = buildArgs(imgAPath, imgBPath); + QProcess::execute("ffmpeg", snapArgsA); + QProcess::execute("ffmpeg", snapArgsB); - if (compArgs.isEmpty()) + if (QFile::exists(imgAPath) && QFile::exists(imgBPath)) { - QTextStream(stderr) << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd << Qt::endl; - } - else - { - QProcess snapFromVidA; - QProcess snapFromVidB; QProcess extComp; - snapFromVidA.start("ffmpeg", snapArgsA); - snapFromVidA.waitForFinished(); - - snapFromVidB.start("ffmpeg", snapArgsB); - snapFromVidB.waitForFinished(); - extComp.start(compArgs[0], compArgs.mid(1)); extComp.waitForFinished(); float score = 0; - auto ok = true; if (shared->outputType == "stdout") { score = getFloatFromExe(extComp.readAllStandardOutput()); } - else if (shared->outputType == "stderr") + else { score = getFloatFromExe(extComp.readAllStandardError()); } - else - { - ok = false; - } - if (!ok) - { - QTextStream(stderr) << "err: img_comp_out: " << shared->outputType << " is not valid. it must be 'stdout' or 'stderr'" << Qt::endl; - } - else - { - QTextStream(stdout) << compArgs.join(" ") << " --result: " << QString::number(score) << Qt::endl; + QTextStream(stdout) << compArgs.join(" ") << " --result: " << QString::number(score) << Qt::endl; - if (score >= shared->imgThresh) + if (eventQue.inQue) + { + if (eventQue.score < score) { - QTextStream(stdout) << "--threshold_breached: " << QString::number(shared->imgThresh) << Qt::endl; - - evt_t event; - - event.timeStamp = curDT; - event.score = score; - event.imgPath = imgBPath; - event.queAge = 0; - - mod = true; - - shared->recList.append(event); + eventQue.score = score; + eventQue.imgPath = imgBPath; } + + eventQue.vidList.append(vidAPath); + eventQue.vidList.append(vidBPath); + + if (eventQue.queAge >= shared->evMaxSecs) + { + eventQue.inQue = false; + + shared->recList.append(eventQue); + + eventQue.imgPath.clear(); + eventQue.vidList.clear(); + eventQue.timeStamp.clear(); + + eventQue.score = 0; + eventQue.queAge = 0; + } + } + else if (score >= shared->imgThresh) + { + QTextStream(stdout) << "--threshold_breached: " << QString::number(shared->imgThresh) << Qt::endl; + + eventQue.score = score; + eventQue.imgPath = imgBPath; + eventQue.inQue = true; + eventQue.queAge = 0; + eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT); + + eventQue.vidList.clear(); + eventQue.vidList.append(vidAPath); + eventQue.vidList.append(vidBPath); } } } diff --git a/src/camera.h b/src/camera.h index 3897cbf..b9f5e75 100644 --- a/src/camera.h +++ b/src/camera.h @@ -55,30 +55,13 @@ public: virtual bool exec(); }; -class Upkeep : public Loop -{ - Q_OBJECT - -public: - - explicit Upkeep(shared_t *shared, QThread *thr, QObject *parent = nullptr); - - bool exec(); -}; - class EventLoop : public Loop { Q_OBJECT private: - QStringList vidList; - QString imgPath; - QString name; - float highScore; - uint cycles; - - bool wrOutVod(); + bool wrOutVod(const evt_t &event); public: @@ -94,9 +77,7 @@ class DetectLoop : public Loop private: QTimer *pcTimer; - uint delayCycles; - bool mod; - quint64 seed; + evt_t eventQue; void resetTimers(); float getFloatFromExe(const QByteArray &line); diff --git a/src/common.cpp b/src/common.cpp index 73bc1ce..a950d12 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -129,7 +129,7 @@ void enforceMaxImages(shared_t *share) { auto names = lsFilesInDir(share->buffPath + "/img", ".bmp"); - while (names.size() > MAX_IMAGES) + while (names.size() > (share->liveSecs / 2)) { QFile::remove(share->buffPath + "/img/" + names[0]); @@ -137,11 +137,11 @@ void enforceMaxImages(shared_t *share) } } -void enforceMaxVids(shared_t *share) +void enforceMaxClips(shared_t *share) { auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt); - while (names.size() > MAX_VIDEOS) + while (names.size() > (share->liveSecs / 2)) { QFile::remove(share->buffPath + "/live/" + names[0]); @@ -217,8 +217,7 @@ bool rdConf(const QString &filePath, shared_t *share) share->retCode = 0; share->imgThresh = 8000; - share->maxEvents = 100; - share->maxLogSize = 100000; + share->maxEvents = 30; share->skipCmd = false; share->postSecs = 60; share->evMaxSecs = 30; @@ -226,12 +225,13 @@ bool rdConf(const QString &filePath, shared_t *share) share->outputType = "stderr"; share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null"; share->streamCodec = "copy"; - share->streamExt = ".ts"; - share->recExt = ".mp4"; + share->streamExt = ".avi"; + share->recExt = ".avi"; share->thumbExt = ".jpg"; share->imgThreads = thrCount; share->recThreads = thrCount; share->recFps = 30; + share->liveSecs = 80; share->recScale = "1280:720"; share->imgScale = "320:240"; share->servUser = APP_BIN; @@ -253,7 +253,6 @@ bool rdConf(const QString &filePath, shared_t *share) rdLine("post_cmd = ", line, &share->postCmd); rdLine("img_thresh = ", line, &share->imgThresh); rdLine("max_events = ", line, &share->maxEvents); - rdLine("max_log_size = ", line, &share->maxLogSize); rdLine("img_comp_out = ", line, &share->outputType); rdLine("img_comp_cmd = ", line, &share->compCmd); rdLine("stream_codec = ", line, &share->streamCodec); @@ -266,18 +265,24 @@ bool rdConf(const QString &filePath, shared_t *share) rdLine("rec_scale = ", line, &share->recScale); rdLine("img_scale = ", line, &share->imgScale); rdLine("service_user = ", line, &share->servUser); + rdLine("live_secs = ", line, &share->liveSecs); } } while(!line.isEmpty()); if (share->camName.isEmpty()) { - share->camName = QFileInfo(share->conf).fileName(); + share->camName = QFileInfo(share->conf).baseName(); } extCorrection(share->streamExt); extCorrection(share->recExt); extCorrection(share->thumbExt); + + if (share->outputType != "stdout" && share->outputType != "stderr") + { + share->outputType = "stderr"; + } if (share->buffPath.isEmpty()) { @@ -296,6 +301,16 @@ bool rdConf(const QString &filePath, shared_t *share) { share->recPath = QDir::cleanPath(share->recPath); } + + if (share->liveSecs < 10) + { + share->liveSecs = 10; + } + + if ((share->liveSecs - 4) < share->evMaxSecs) + { + share->evMaxSecs = share->liveSecs - 4; + } share->retCode = EACCES; diff --git a/src/common.h b/src/common.h index 7529119..6a2e76c 100644 --- a/src/common.h +++ b/src/common.h @@ -29,42 +29,34 @@ using namespace std; -#define APP_VER "3.3.t3" +#define APP_VER "3.3.t4" #define APP_NAME "Motion Watch" #define APP_BIN "mow" -#define REC_LOG_NAME "rec.log" -#define DET_LOG_NAME "det.log" -#define UPK_LOG_NAME "upk.log" #define DATETIME_FMT "yyyyMMddhhmmss" #define STRFTIME_FMT "%Y%m%d%H%M%S" -#define MAX_IMAGES 1000 -#define MAX_VIDEOS 1000 #define PREV_IMG "&prev&" #define NEXT_IMG "&next&" enum CmdExeType { MAIN_LOOP, - VID_LOOP, - IMG_LOOP + VID_LOOP }; struct evt_t { - QDateTime timeStamp; - QString imgPath; - float score; - uint queAge; + QList vidList; + QString timeStamp; + QString imgPath; + float score; + bool inQue; + int queAge; }; struct shared_t { QList recList; - QMutex recMutex; - QMutex logMutex; QString conf; - QString recLog; - QString detLog; QString recordUri; QString buffPath; QString postCmd; @@ -80,10 +72,10 @@ struct shared_t QString imgScale; QString servMainLoop; QString servVidLoop; - QString servImgLoop; QString servUser; bool singleTenant; bool skipCmd; + int liveSecs; int recFps; int imgThreads; int recThreads; @@ -91,7 +83,6 @@ struct shared_t int postSecs; int imgThresh; int maxEvents; - int maxLogSize; int retCode; }; @@ -110,7 +101,7 @@ void rdLine(const QString ¶m, const QString &line, int *value); void rdLine(const QString ¶m, const QString &line, bool *value); void enforceMaxEvents(shared_t *share); void enforceMaxImages(shared_t *share); -void enforceMaxVids(shared_t *share); +void enforceMaxClips(shared_t *share); void extCorrection(QString &ext); #endif // COMMON_H diff --git a/src/main.cpp b/src/main.cpp index 66b3e5a..c74698b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ void showHelp() QTextStream(stdout) << "-i : all valid config files found in /etc/mow will be used to create" << Qt::endl; QTextStream(stdout) << " a full instance; full meaning main and vid loop systemd services" << Qt::endl; QTextStream(stdout) << " will be created for each config file." << Qt::endl; + QTextStream(stdout) << "-d : this is the same as -i except it will not auto start the services." << Qt::endl; QTextStream(stdout) << "-v : display the current version." << Qt::endl; QTextStream(stdout) << "-u : uninstall the entire app from your system, including all" << Qt::endl; QTextStream(stdout) << " systemd services related to it." << Qt::endl; diff --git a/src/services.cpp b/src/services.cpp index 55adaf0..fbe57ef 100644 --- a/src/services.cpp +++ b/src/services.cpp @@ -103,17 +103,21 @@ QString camCmdFromConf(shared_t *conf, CmdExeType type) } else { - ret += "taskset -c " + buildThreadCount(conf->recThreads) + " "; - ret += "ffmpeg -hide_banner -i '" + conf->recordUri + "' -strftime 1 -strftime_mkdir 1 "; + ret += "ffmpeg -hide_banner -y -i '" + conf->recordUri + "' -strftime 1 -strftime_mkdir 1 "; if (conf->recordUri.contains("rtsp")) { ret += "-rtsp_transport tcp "; } - - ret += "-vf fps=" + QString::number(conf->recFps) + ",scale=" + conf->recScale + " "; - ret += "-hls_segment_filename live/" + QString(STRFTIME_FMT) + conf->streamExt + " "; - ret += "-y -vcodec " + conf->streamCodec + " -f hls -hls_time 2 -hls_list_size 1000 -hls_flags append_list+omit_endlist -t 60 stream.m3u8"; + + if (conf->streamCodec != "copy") + { + ret += "-vf fps=" + QString::number(conf->recFps) + ",scale=" + conf->recScale + " "; + } + + ret += "-vcodec " + conf->streamCodec + " "; + ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames \"expr:gte(t, n_forced * 2)\" -t 60 -segment_time 2 -f segment "; + ret += conf->buffPath + "/live/" + QString(STRFTIME_FMT) + conf->streamExt; } return ret; @@ -150,8 +154,8 @@ int rmService(const QString &servName) if (QFile::exists(path)) { - ret = QProcess::execute("systemctl", {"stop", servName}); - ret = QProcess::execute("systemctl", {"disable", servName}); + if (ret == 0) ret = QProcess::execute("systemctl", {"stop", servName}); + if (ret == 0) ret = QProcess::execute("systemctl", {"disable", servName}); QFile::remove(path); QFile::remove("/usr/bin/" + servName); @@ -168,6 +172,12 @@ int rmServiceByConf(const QString &confFile) { conf.retCode = rmService(conf.servMainLoop); conf.retCode = rmService(conf.servVidLoop); + + QDir live(conf.buffPath + "/live"); + QDir img(conf.buffPath + "/img"); + + live.removeRecursively(); + img.removeRecursively(); } return conf.retCode;