From 23e0ae935ecbbb68f5968adb3124cda1d91d9173 Mon Sep 17 00:00:00 2001 From: Maurice ONeal Date: Sun, 5 Feb 2023 14:05:56 -0500 Subject: [PATCH] v1.6.t3 Added string trimming to the vid_container parameter to filter out bad user input. Added detection_stream url to the config file and made it so the application can now use a smaller/lower bit rate stream for motion detection separate from the recording stream. This can significantly lower CPU usage. Moved away from using system() and the explicit timeout command. Instead opted to using popen() and cancelable pthreads. Doing this pulls back more control over ffmpeg than before and the app will now properly respond term signals and even the CTRL-C keyboard interrupt. --- src/common.cpp | 50 ++++++++++++++++++++++++++ src/common.h | 16 +++++++-- src/main.cpp | 97 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 131 insertions(+), 32 deletions(-) diff --git a/src/common.cpp b/src/common.cpp index 8291956..efa92b7 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -12,6 +12,44 @@ #include "common.h" +string leftTrim(const string &str, const string &toRemove) +{ + auto start = str.find_first_not_of(toRemove); + + if (start != string::npos) + { + return str.substr(start); + } + else + { + return str; + } +} + +string rightTrim(const string &str, const string &toRemove) +{ + auto end = str.find_last_not_of(toRemove); + + if (end != string::npos) + { + return str.substr(0, end + 1); + } + else + { + return str; + } +} + +string trim(const string &str, const string &toRemove) +{ + return rightTrim(leftTrim(str, toRemove), toRemove); +} + +string trim(const string &str) +{ + return trim(str, TRIM_REMOVE); +} + string cleanDir(const string &path) { if (path[path.size() - 1] == '/') @@ -189,6 +227,7 @@ bool rdConf(const string &filePath, shared_t *share) { rdLine("cam_name = ", line, &share->camName); rdLine("recording_stream = ", line, &share->recordUrl); + rdLine("detect_stream = ", line, &share->detectUrl); rdLine("web_root = ", line, &share->webRoot); rdLine("web_text = ", line, &share->webTxt); rdLine("web_bg = ", line, &share->webBg); @@ -216,6 +255,7 @@ bool rdConf(const string &filePath, shared_t *share) bool rdConf(shared_t *share) { share->recordUrl.clear(); + share->detectUrl.clear(); share->postCmd.clear(); share->buffDir.clear(); share->camName.clear(); @@ -241,6 +281,8 @@ bool rdConf(shared_t *share) share->webBg = "#485564"; share->webTxt = "#dee5ee"; share->webFont = "courier"; + share->detSuffix = ".det."; + share->recSuffix = ".rec."; auto ret = false; @@ -256,10 +298,18 @@ bool rdConf(shared_t *share) share->camName = path(share->conf.back()).filename(); } + if (share->detectUrl.empty()) + { + share->detectUrl = share->recordUrl; + } + share->outDir = cleanDir(share->webRoot) + "/" + share->camName; share->buffDir = cleanDir(share->buffDir) + "/" + share->camName; share->recLogPath = share->outDir + "/rec_log_lines.html"; share->detLogPath = share->outDir + "/det_log_lines.html"; + share->vidExt = trim(share->vidExt); + share->detSuffix += share->vidExt; + share->recSuffix += share->vidExt; if (share->init) { diff --git a/src/common.h b/src/common.h index c375b5f..9fbd04d 100644 --- a/src/common.h +++ b/src/common.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -35,8 +36,9 @@ using namespace cv; using namespace std; using namespace std::filesystem; -#define APP_VER "1.6.t2" -#define APP_NAME "Motion Watch" +#define APP_VER "1.6.t3" +#define APP_NAME "Motion Watch" +#define TRIM_REMOVE " \n\r\t\f\v." struct shared_t { @@ -47,9 +49,14 @@ struct shared_t string recLogPath; string detLogPath; string recordUrl; + string detectUrl; string outDir; string postCmd; string buffDir; + string recSuffix; + string detSuffix; + string recCmd; + string detCmd; string vidExt; string vidCodec; string camName; @@ -59,6 +66,7 @@ struct shared_t string webRoot; bool init; bool skipCmd; + int cmdFinished; int clipLen; int frameGap; int pixThresh; @@ -70,6 +78,10 @@ struct shared_t int retCode; }; +string leftTrim(const string &str, const string &toRemove); +string rightTrim(const string &str, const string &toRemove); +string trim(const string &str, const string &toRemove); +string trim(const string &str); string genDstFile(const string &dirOut, const char *fmt, const string &ext); string genTimeStr(const char *fmt); string cleanDir(const string &path); diff --git a/src/main.cpp b/src/main.cpp index 8aec3d4..00985bd 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,26 +13,50 @@ #include "mo_detect.h" #include "logger.h" -void detectMoInFile(const string &bufPath, shared_t *share) +void detectMoInFile(const string &detPath, const string &recPath, shared_t *share) { detLog("detect_mo_in_file() -- start", share); Mat thumbNail; - if (moDetect(bufPath, thumbNail, share)) + if (moDetect(detPath, thumbNail, share)) { share->skipCmd = true; - wrOut(bufPath, thumbNail, share); - } - else if (exists(bufPath)) - { - remove(bufPath); + wrOut(recPath, thumbNail, share); } + if (exists(detPath)) remove(detPath); + if (exists(recPath)) remove(recPath); + detLog("detect_mo_in_file() -- finished", share); } +static void *runDetCmd(void *arg) +{ + auto share = reinterpret_cast(arg); + + recLog("ffmpeg_det_run: " + share->detCmd, share); + pclose(popen(share->detCmd.c_str(), "r")); + + share->cmdFinished++; return NULL; +} + +static void *runRecCmd(void *arg) +{ + auto share = reinterpret_cast(arg); + + if (share->recordUrl != share->detectUrl) + { + // recording command is not allowed to run if both + // streams are the same. + recLog("ffmpeg_rec_run: " + share->recCmd, share); + pclose(popen(share->recCmd.c_str(), "r")); + } + + share->cmdFinished++; return NULL; +} + void recLoop(shared_t *share) { while (rdConf(share)) @@ -68,38 +92,51 @@ void recLoop(shared_t *share) for (auto i = 0; i < share->numOfClips; ++i) { - auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt; - auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " "; + auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix; + auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix; - cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t " + to_string(share->clipLen) + " " + bufPath; - - recLog("ffmpeg_run: " + cmd, share); - - auto retCode = system(cmd.c_str()); - - recLog("ffmpeg_retcode: " + to_string(retCode), share); - - if (retCode == 0) + if (share->recordUrl == share->detectUrl) { - recLog("detect_mo_in_file() -- started in a seperate thread.", share); + recPath = detPath; + } - share->detThreads.push_back(thread(detectMoInFile, bufPath, share)); + share->recCmd = "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + recPath; + share->detCmd = "ffmpeg -hide_banner -i " + share->detectUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + detPath; + + share->cmdFinished = 0; + + pthread_t detThr; + pthread_t recThr; + + auto detOk = pthread_create(&detThr, NULL, &runDetCmd, share); + auto recOk = pthread_create(&recThr, NULL, &runRecCmd, share); + + if (detOk == 0 && recOk == 0) + { + sleep(share->clipLen); + + if (share->cmdFinished < 2) + { + // 2 second grace period. + sleep(2); + } + + if (share->cmdFinished < 2) + { + // force close ffmpeg cmds after failing to finish + // after the grace period. + pthread_cancel(detThr); + pthread_cancel(recThr); + } + + new thread(detectMoInFile, detPath, recPath, share); } else { - recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share); - - if (exists(bufPath)) - { - remove(bufPath); - } - - sleep(share->clipLen); + recLog("could not start one or both ffmpeg cmds in seperate threads. detOk = " + to_string(detOk) + " recOk = " + to_string(recOk), share); } } - waitForDetThreads(share); - if (!share->skipCmd) { recLog("no motion detected", share);