Updated the documentation.

The test machine had a mystery crash that needs to be investigated. In
mean time, the timeout run code has been refactored and will not run
thread cancel unless is it absolutely needed at the individual thread
level (hopefully that fixes the crash issue).

post_cmd shall also now run via timeout. With that, no external commands
should cause this application to stall. Timeout protection should
prevent that.
This commit is contained in:
Maurice ONeal 2023-02-07 23:19:41 -05:00
parent 23e0ae935e
commit f4f1f62d25
3 changed files with 132 additions and 45 deletions

View File

@ -38,6 +38,14 @@ recording_stream = rtsp://1.2.3.4:554/h264
# this is the url to the main stream of the IP camera that will be used # this is the url to the main stream of the IP camera that will be used
# to record footage. # to record footage.
# #
detect_stream = rtsp://1.2.3.4:554/h264cif
# this is an optional detection stream that will be used to detect motion.
# most cameras have the option to broadcast multiple streams of the same
# footage. it's recommend to use this parameter with a smaller, lower bit
# rate stream to reduce CPU usage. recording_stream will still be used to
# record higher quality footage. if not defined, recording_stream will be
# used for both motion detection and recording.
#
web_root = /var/www/html web_root = /var/www/html
# this is the output directory that will be used to store recorded footage # this is the output directory that will be used to store recorded footage
# from the cameras as well as the web interface for the application. # from the cameras as well as the web interface for the application.

View File

@ -36,7 +36,7 @@ using namespace cv;
using namespace std; using namespace std;
using namespace std::filesystem; using namespace std::filesystem;
#define APP_VER "1.6.t3" #define APP_VER "1.6.t4"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define TRIM_REMOVE " \n\r\t\f\v." #define TRIM_REMOVE " \n\r\t\f\v."
@ -55,8 +55,6 @@ struct shared_t
string buffDir; string buffDir;
string recSuffix; string recSuffix;
string detSuffix; string detSuffix;
string recCmd;
string detCmd;
string vidExt; string vidExt;
string vidCodec; string vidCodec;
string camName; string camName;
@ -78,6 +76,14 @@ struct shared_t
int retCode; int retCode;
}; };
struct cmdRes_t
{
string cmd;
pthread_t thr;
int ret;
bool finished;
};
string leftTrim(const string &str, const string &toRemove); string leftTrim(const string &str, const string &toRemove);
string rightTrim(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, const string &toRemove);

View File

@ -32,29 +32,109 @@ void detectMoInFile(const string &detPath, const string &recPath, shared_t *shar
detLog("detect_mo_in_file() -- finished", share); detLog("detect_mo_in_file() -- finished", share);
} }
static void *runDetCmd(void *arg) static void *runCmd(void *arg)
{ {
auto share = reinterpret_cast<shared_t*>(arg); auto args = static_cast<cmdRes_t*>(arg);
recLog("ffmpeg_det_run: " + share->detCmd, share); args->finished = false;
pclose(popen(share->detCmd.c_str(), "r")); args->ret = 0;
share->cmdFinished++; return NULL; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
args->ret = pclose(popen(args->cmd.c_str(), "r"));
args->finished = true;
return NULL;
} }
static void *runRecCmd(void *arg) bool allCmdsFinished(const vector<cmdRes_t*> &resList)
{ {
auto share = reinterpret_cast<shared_t*>(arg); for (auto &&res : resList)
if (share->recordUrl != share->detectUrl)
{ {
// recording command is not allowed to run if both if (!res->finished) return false;
// streams are the same.
recLog("ffmpeg_rec_run: " + share->recCmd, share);
pclose(popen(share->recCmd.c_str(), "r"));
} }
share->cmdFinished++; return NULL; return true;
}
void cancelAllCmds(const vector<cmdRes_t*> &resList)
{
for (auto &&res : resList)
{
if (!res->finished)
{
pthread_cancel(res->thr);
}
}
}
bool allCmdsDidNotFail(const vector<cmdRes_t*> &resList)
{
for (auto &&res : resList)
{
if (res->ret != 0) return false;
}
return true;
}
void cleanupRes(vector<cmdRes_t*> &resList)
{
for (auto &&res : resList)
{
delete res;
}
}
void runTOCmds(int timeOut, const vector<string> &cmds, vector<cmdRes_t*> &resList, bool abs = true)
{
resList.clear();
for (auto &&cmd : cmds)
{
auto res = new struct cmdRes_t;
res->cmd = cmd;
res->finished = false;
res->ret = pthread_create(&res->thr, NULL, &runCmd, res);
resList.push_back(res);
}
if (abs)
{
sleep(timeOut);
if (!allCmdsFinished(resList))
{
sleep(2);
}
cancelAllCmds(resList);
}
else
{
for (auto i = 0; !allCmdsFinished(resList); ++i)
{
sleep(1);
if (i >= timeOut)
{
cancelAllCmds(resList); break;
}
}
}
}
void runTOCmd(int timeout, const string &cmd, bool abs = true)
{
vector<string> cmds;
vector<cmdRes_t*> rets;
cmds.push_back(cmd);
runTOCmds(timeout, cmds, rets, abs);
cleanupRes(rets);
} }
void recLoop(shared_t *share) void recLoop(shared_t *share)
@ -95,45 +175,38 @@ void recLoop(shared_t *share)
auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix; auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix;
auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix; auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix;
vector<string> cmds;
vector<cmdRes_t*> rets;
if (share->recordUrl == share->detectUrl) if (share->recordUrl == share->detectUrl)
{ {
recPath = detPath; recPath = detPath;
} }
else
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); cmds.push_back("ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + recPath);
}
if (share->cmdFinished < 2) cmds.push_back("ffmpeg -hide_banner -i " + share->detectUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + detPath);
{
// 2 second grace period.
sleep(2);
}
if (share->cmdFinished < 2) recLog("fetching camera footage -- ", share);
{
// force close ffmpeg cmds after failing to finish
// after the grace period.
pthread_cancel(detThr);
pthread_cancel(recThr);
}
runTOCmds(share->clipLen, cmds, rets);
if (allCmdsDidNotFail(rets))
{
new thread(detectMoInFile, detPath, recPath, share); new thread(detectMoInFile, detPath, recPath, share);
} }
else else
{ {
recLog("could not start one or both ffmpeg cmds in seperate threads. detOk = " + to_string(detOk) + " recOk = " + to_string(recOk), share); recLog("one or both fetch cmds failed.", share);
}
recLog("fetch results:", share);
for (auto &&ret : rets)
{
recLog("cmd: " + ret->cmd + " return_code: " + to_string(ret->ret), share);
} }
} }
@ -148,7 +221,7 @@ void recLoop(shared_t *share)
else else
{ {
recLog("running post command: " + share->postCmd, share); recLog("running post command: " + share->postCmd, share);
system(share->postCmd.c_str()); runTOCmd(14, share->postCmd, false);
} }
} }
else else