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"
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)
{

View File

@ -22,6 +22,7 @@
#include <errno.h>
#include <vector>
#include <thread>
#include <pthread.h>
#include <filesystem>
#include <mutex>
#include <sys/types.h>
@ -35,8 +36,9 @@ using namespace cv;
using namespace std;
using namespace std::filesystem;
#define APP_VER "1.6.t2"
#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);

View File

@ -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<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)
{
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);