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.
This commit is contained in:
parent
80f8ec07e3
commit
23e0ae935e
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
14
src/common.h
14
src/common.h
|
@ -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);
|
||||||
|
|
97
src/main.cpp
97
src/main.cpp
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user