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.
This commit is contained in:
Maurice ONeal 2023-02-05 14:05:56 -05:00
parent 80f8ec07e3
commit 23e0ae935e
3 changed files with 131 additions and 32 deletions

View File

@ -12,6 +12,44 @@
#include "common.h" #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) string cleanDir(const string &path)
{ {
if (path[path.size() - 1] == '/') if (path[path.size() - 1] == '/')
@ -189,6 +227,7 @@ bool rdConf(const string &filePath, shared_t *share)
{ {
rdLine("cam_name = ", line, &share->camName); rdLine("cam_name = ", line, &share->camName);
rdLine("recording_stream = ", line, &share->recordUrl); rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("detect_stream = ", line, &share->detectUrl);
rdLine("web_root = ", line, &share->webRoot); rdLine("web_root = ", line, &share->webRoot);
rdLine("web_text = ", line, &share->webTxt); rdLine("web_text = ", line, &share->webTxt);
rdLine("web_bg = ", line, &share->webBg); rdLine("web_bg = ", line, &share->webBg);
@ -216,6 +255,7 @@ bool rdConf(const string &filePath, shared_t *share)
bool rdConf(shared_t *share) bool rdConf(shared_t *share)
{ {
share->recordUrl.clear(); share->recordUrl.clear();
share->detectUrl.clear();
share->postCmd.clear(); share->postCmd.clear();
share->buffDir.clear(); share->buffDir.clear();
share->camName.clear(); share->camName.clear();
@ -241,6 +281,8 @@ bool rdConf(shared_t *share)
share->webBg = "#485564"; share->webBg = "#485564";
share->webTxt = "#dee5ee"; share->webTxt = "#dee5ee";
share->webFont = "courier"; share->webFont = "courier";
share->detSuffix = ".det.";
share->recSuffix = ".rec.";
auto ret = false; auto ret = false;
@ -256,10 +298,18 @@ bool rdConf(shared_t *share)
share->camName = path(share->conf.back()).filename(); share->camName = path(share->conf.back()).filename();
} }
if (share->detectUrl.empty())
{
share->detectUrl = share->recordUrl;
}
share->outDir = cleanDir(share->webRoot) + "/" + share->camName; share->outDir = cleanDir(share->webRoot) + "/" + share->camName;
share->buffDir = cleanDir(share->buffDir) + "/" + share->camName; share->buffDir = cleanDir(share->buffDir) + "/" + share->camName;
share->recLogPath = share->outDir + "/rec_log_lines.html"; share->recLogPath = share->outDir + "/rec_log_lines.html";
share->detLogPath = share->outDir + "/det_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) if (share->init)
{ {

View File

@ -22,6 +22,7 @@
#include <errno.h> #include <errno.h>
#include <vector> #include <vector>
#include <thread> #include <thread>
#include <pthread.h>
#include <filesystem> #include <filesystem>
#include <mutex> #include <mutex>
#include <sys/types.h> #include <sys/types.h>
@ -35,8 +36,9 @@ using namespace cv;
using namespace std; using namespace std;
using namespace std::filesystem; using namespace std::filesystem;
#define APP_VER "1.6.t2" #define APP_VER "1.6.t3"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define TRIM_REMOVE " \n\r\t\f\v."
struct shared_t struct shared_t
{ {
@ -47,9 +49,14 @@ struct shared_t
string recLogPath; string recLogPath;
string detLogPath; string detLogPath;
string recordUrl; string recordUrl;
string detectUrl;
string outDir; string outDir;
string postCmd; string postCmd;
string buffDir; string buffDir;
string recSuffix;
string detSuffix;
string recCmd;
string detCmd;
string vidExt; string vidExt;
string vidCodec; string vidCodec;
string camName; string camName;
@ -59,6 +66,7 @@ struct shared_t
string webRoot; string webRoot;
bool init; bool init;
bool skipCmd; bool skipCmd;
int cmdFinished;
int clipLen; int clipLen;
int frameGap; int frameGap;
int pixThresh; int pixThresh;
@ -70,6 +78,10 @@ struct shared_t
int retCode; 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 genDstFile(const string &dirOut, const char *fmt, const string &ext);
string genTimeStr(const char *fmt); string genTimeStr(const char *fmt);
string cleanDir(const string &path); string cleanDir(const string &path);

View File

@ -13,26 +13,50 @@
#include "mo_detect.h" #include "mo_detect.h"
#include "logger.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); detLog("detect_mo_in_file() -- start", share);
Mat thumbNail; Mat thumbNail;
if (moDetect(bufPath, thumbNail, share)) if (moDetect(detPath, thumbNail, share))
{ {
share->skipCmd = true; share->skipCmd = true;
wrOut(bufPath, thumbNail, share); wrOut(recPath, thumbNail, share);
}
else if (exists(bufPath))
{
remove(bufPath);
} }
if (exists(detPath)) remove(detPath);
if (exists(recPath)) remove(recPath);
detLog("detect_mo_in_file() -- finished", share); detLog("detect_mo_in_file() -- finished", share);
} }
static void *runDetCmd(void *arg)
{
auto share = reinterpret_cast<shared_t*>(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<shared_t*>(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) void recLoop(shared_t *share)
{ {
while (rdConf(share)) while (rdConf(share))
@ -68,38 +92,51 @@ void recLoop(shared_t *share)
for (auto i = 0; i < share->numOfClips; ++i) for (auto i = 0; i < share->numOfClips; ++i)
{ {
auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt; auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix;
auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " "; 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; if (share->recordUrl == share->detectUrl)
recLog("ffmpeg_run: " + cmd, share);
auto retCode = system(cmd.c_str());
recLog("ffmpeg_retcode: " + to_string(retCode), share);
if (retCode == 0)
{ {
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 else
{ {
recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share); recLog("could not start one or both ffmpeg cmds in seperate threads. detOk = " + to_string(detOk) + " recOk = " + to_string(recOk), share);
if (exists(bufPath))
{
remove(bufPath);
}
sleep(share->clipLen);
} }
} }
waitForDetThreads(share);
if (!share->skipCmd) if (!share->skipCmd)
{ {
recLog("no motion detected", share); recLog("no motion detected", share);