Fixed the crashing issue by adding tcp timeout args to ffmpeg and
having the app handle empty frames from a disconnected camera
better.

Reformed the directory structure by having live, logs and events in
seperate directories.

schLoop() no longer exists, postCmd is now handled by
detectMoInStream() to ensure motion detection is not done while the
command is running.
This commit is contained in:
Zii 2023-03-26 10:32:56 -04:00
parent 93723bb7b1
commit 83b206c06c
8 changed files with 127 additions and 132 deletions

View File

@ -134,16 +134,17 @@ void cleanupStream(const string &plsPath)
void enforceMaxEvents(shared_t *share)
{
auto names = lsFilesInDir(".", ".mp4");
auto names = lsFilesInDir("events", ".mp4");
while (names.size() > share->maxEvents)
{
// removes the playlist file extension.
auto nameOnly = names[0].substr(0, names[0].size() - 4);
auto imgFile = nameOnly + ".jpg";
auto webFile = nameOnly + ".html";
// removes the video file extension (.mp4).
auto nameOnly = "events/" + names[0].substr(0, names[0].size() - 4);
auto mp4File = nameOnly + string(".mp4");
auto imgFile = nameOnly + string(".jpg");
auto webFile = nameOnly + string(".html");
if (exists(names[0])) remove(names[0]);
if (exists(mp4File)) remove(mp4File);
if (exists(imgFile)) remove(imgFile);
if (exists(webFile)) remove(webFile);

View File

@ -38,7 +38,7 @@ using namespace std;
using namespace std::filesystem;
using namespace std::chrono;
#define APP_VER "2.0.t12"
#define APP_VER "2.0.t13"
#define APP_NAME "Motion Watch"
#define REC_LOG_NAME "rec_log_lines.html"
#define DET_LOG_NAME "det_log_lines.html"
@ -67,8 +67,9 @@ struct shared_t
string webTxt;
string webFont;
string webRoot;
mutex detMutex;
bool skipCmd;
bool postCmdRunning;
int procTime;
int schSec;
int frameGap;
int pixThresh;

View File

@ -38,7 +38,7 @@ void enforceMaxLogSize(const string &filePath, shared_t *share)
}
}
void dumpLogs(const char *fileName, const string &lines)
void dumpLogs(const string &fileName, const string &lines)
{
if (!lines.empty())
{
@ -46,11 +46,11 @@ void dumpLogs(const char *fileName, const string &lines)
if (exists(fileName))
{
outFile.open(fileName, ofstream::app);
outFile.open(fileName.c_str(), ofstream::app);
}
else
{
outFile.open(fileName);
outFile.open(fileName.c_str());
}
outFile << lines;
@ -116,7 +116,7 @@ void initLogFrontPage(const string &filePath, const string &logLinesFile)
void initLogFrontPages(shared_t *share)
{
initLogFrontPage("recording_log.html", REC_LOG_NAME);
initLogFrontPage("detection_log.html", DET_LOG_NAME);
initLogFrontPage("upkeep_log.html", UPK_LOG_NAME);
initLogFrontPage("logs/recording_log.html", REC_LOG_NAME);
initLogFrontPage("logs/detection_log.html", DET_LOG_NAME);
initLogFrontPage("logs/upkeep_log.html", UPK_LOG_NAME);
}

View File

@ -18,7 +18,7 @@
void recLog(const string &line, shared_t *share);
void detLog(const string &line, shared_t *share);
void upkLog(const string &line, shared_t *share);
void dumpLogs(const char *fileName, const string &lines);
void dumpLogs(const string &fileName, const string &lines);
void enforceMaxLogSize(const string &filePath, shared_t *share);
void initLogFrontPages(shared_t *share);

88
src/main.cpp Executable file → Normal file
View File

@ -27,24 +27,26 @@ void eventLoop(shared_t *share)
{
while (share->retCode == 0)
{
for (auto it = share->recList.begin(); !share->recList.empty();)
while (!share->recList.empty())
{
auto it = share->recList.begin();
auto evName = it->first;
auto event = it->second;
auto timeDiff = genEpoch() - event.createTime;
// wait at least 61 seconds before processing the event in
// wait at least 62 seconds before processing the event in
// queue.
if ((timeDiff > 0) && (timeDiff > 60))
if ((timeDiff > 0) && (timeDiff > 62))
{
try
{
createDirTree("events");
wrOutVod(event, share);
genHTMLvod(evName);
if (!exists(evName + ".jpg"))
if (!exists("events/" + evName + ".jpg"))
{
imwrite(string(evName + ".jpg").c_str(), event.thumbnail);
imwrite(string("events/" + evName + ".jpg").c_str(), event.thumbnail);
}
}
catch (filesystem_error &ex)
@ -54,36 +56,11 @@ void eventLoop(shared_t *share)
share->recList.erase(it);
}
}
sleep(5);
}
}
void schLoop(shared_t *share)
{
if (!share->postCmd.empty())
{
while (share->retCode == 0)
{
sleep(share->schSec);
if (!share->skipCmd)
{
share->postCmdRunning = true;
detLog("no motion detected, running post command: " + share->postCmd, share);
system(share->postCmd.c_str());
share->postCmdRunning = false;
}
else
{
share->skipCmd = false;
detLog("motion detected, skipping the post command.", share);
}
}
sleep(5);
}
}
@ -91,13 +68,17 @@ void upkeep(shared_t *share)
{
while (share->retCode == 0)
{
enforceMaxLogSize(REC_LOG_NAME, share);
enforceMaxLogSize(DET_LOG_NAME, share);
enforceMaxLogSize(UPK_LOG_NAME, share);
createDirTree("live");
createDirTree("events");
createDirTree("logs");
dumpLogs(REC_LOG_NAME, share->recLog);
dumpLogs(DET_LOG_NAME, share->detLog);
dumpLogs(UPK_LOG_NAME, share->upkLog);
enforceMaxLogSize(string("logs/") + REC_LOG_NAME, share);
enforceMaxLogSize(string("logs/") + DET_LOG_NAME, share);
enforceMaxLogSize(string("logs/") + UPK_LOG_NAME, share);
dumpLogs(string("logs/") + REC_LOG_NAME, share->recLog);
dumpLogs(string("logs/") + DET_LOG_NAME, share->detLog);
dumpLogs(string("logs/") + UPK_LOG_NAME, share->upkLog);
share->recLog.clear();
share->detLog.clear();
@ -105,7 +86,6 @@ void upkeep(shared_t *share)
initLogFrontPages(share);
enforceMaxEvents(share);
cleanupEmptyDirs("VIDEO_TS");
genHTMLul(".", share->camName, share);
@ -134,14 +114,19 @@ void recLoop(shared_t *share)
{
while (share->retCode == 0)
{
auto cmd = "ffmpeg -hide_banner -i " +
if (exists("live"))
{
remove_all("live");
}
auto cmd = "ffmpeg -hide_banner -rtsp_transport tcp -timeout 3000000 -i " +
share->recordUrl +
" -strftime 1" +
" -strftime_mkdir 1" +
" -hls_segment_filename 'VIDEO_TS/live/%Y/%j/%H/%M%S.ts'" +
" -hls_segment_filename 'live/%Y-%j-%H-%M-%S.ts'" +
" -hls_flags delete_segments" +
" -y -vcodec copy" +
" -f hls -hls_time 6 -hls_list_size " +
to_string((share->maxDays * 86400) / 6) + // 86400 seconds in a day.
" -f hls -hls_time 10 -hls_list_size 400" +
" stream.m3u8";
recLog("ffmpeg_run: " + cmd, share);
@ -155,7 +140,7 @@ void recLoop(shared_t *share)
recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share);
}
sleep(60);
sleep(10);
}
}
@ -184,27 +169,20 @@ int main(int argc, char** argv)
else
{
sharedRes.retCode = 0;
sharedRes.procTime = 0;
sharedRes.skipCmd = false;
sharedRes.postCmdRunning = false;
rdConf(&sharedRes);
if (exists("VIDEO_TS/live"))
{
remove_all("VIDEO_TS/live");
}
auto thr1 = thread(recLoop, &sharedRes);
auto thr2 = thread(upkeep, &sharedRes);
//auto thr3 = thread(detectMo, &sharedRes);
//auto thr4 = thread(eventLoop, &sharedRes);
auto thr5 = thread(schLoop, &sharedRes);
auto thr3 = thread(detectMo, &sharedRes);
auto thr4 = thread(eventLoop, &sharedRes);
thr1.join();
thr2.join();
//thr3.join();
//thr4.join();
thr5.join();
thr3.join();
thr4.join();
return sharedRes.retCode;
}

View File

@ -12,17 +12,31 @@
#include "mo_detect.h"
void detectMoInStream(const string &bufPath, shared_t *share)
void detectMoInStream(const string &streamFile, shared_t *share)
{
ifstream fileIn(bufPath);
if (share->procTime >= share->schSec)
{
if (!share->skipCmd)
{
detLog("no motion detected, running post command: " + share->postCmd, share);
system(share->postCmd.c_str());
}
else
{
share->skipCmd = false;
share->procTime = 0;
detLog("motion detected, skipping the post command.", share);
}
}
ifstream fileIn(streamFile);
string tsPath;
Mat thumbnail;
auto clipPathFilter = genTimeStr("VIDEO_TS/live/%Y/%j/%H/");
for (string line; getline(fileIn, line); )
{
if (line.starts_with(clipPathFilter))
if (line.starts_with("live/"))
{
tsPath = line;
}
@ -53,31 +67,35 @@ void detectMoInStream(const string &bufPath, shared_t *share)
share->skipCmd = true;
}
share->procTime += 10;
}
}
bool imgDiff(Mat &prev, Mat &next, shared_t *share)
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share)
{
auto ret = false;
Mat prevGray;
Mat nextGray;
cvtColor(prev, prev, COLOR_BGR2GRAY);
cvtColor(next, next, COLOR_BGR2GRAY);
cvtColor(prev, prevGray, COLOR_BGR2GRAY);
cvtColor(next, nextGray, COLOR_BGR2GRAY);
Mat diff;
absdiff(prev, next, diff);
absdiff(prevGray, nextGray, diff);
threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
auto diffScore = countNonZero(diff);
score = countNonZero(diff);
detLog("diff_score: " + to_string(diffScore), share);
detLog("diff_score: " + to_string(score) + " tresh: " + to_string(share->imgThresh), share);
return diffScore >= share->imgThresh;
return score >= share->imgThresh;
}
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
{
auto mod = false;
auto maxScore = 0;
auto score = 0;
detLog("stream_clip: " + buffFile, share);
@ -100,13 +118,18 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
{
capture.retrieve(next);
if (!share->postCmdRunning)
{
if (imgDiff(prev, next, share))
{
resize(next, vidThumb, Size(720, 480), INTER_LINEAR);
lock_guard<mutex> guard(share->detMutex);
mod = true; break;
if (!next.empty())
{
if (imgDiff(prev, next, score, share))
{
if (score > maxScore)
{
maxScore = score;
resize(next, vidThumb, Size(720, 480), INTER_LINEAR);
}
}
}
@ -124,7 +147,7 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
capture.release();
return mod;
return maxScore > 0;
}
void wrOutVod(const pls_t &event, shared_t *share)
@ -140,6 +163,6 @@ void wrOutVod(const pls_t &event, shared_t *share)
file.close();
system(string("ffmpeg -f concat -safe 0 -i " + concat + " -c copy " + event.evName + ".mp4").c_str());
system(string("ffmpeg -f concat -safe 0 -i " + concat + " -c copy events/" + event.evName + ".mp4").c_str());
remove(concat);
}

View File

@ -16,9 +16,9 @@
#include "common.h"
#include "logger.h"
bool imgDiff(Mat &prev, Mat &next, shared_t *share);
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share);
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share);
void detectMoInStream(const string &bufPath, shared_t *share);
void detectMoInStream(const string &streamFile, shared_t *share);
void wrOutVod(const pls_t &pls, shared_t *share);
#endif // MO_DETECT_H

View File

@ -15,8 +15,8 @@
void genHTMLul(const string &outputDir, const string &title, shared_t *share)
{
vector<string> logNames;
vector<string> regNames = lsFilesInDir(outputDir);
vector<string> dirNames = lsDirsInDir(outputDir);
vector<string> eveNames;
vector<string> dirNames;
string htmlText = "<!DOCTYPE html>\n";
@ -31,20 +31,23 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "<body>\n";
htmlText += "<h3>" + title + "</h3>\n";
if (!dirNames.empty() && !exists(outputDir + "/VIDEO_TS"))
if (exists(outputDir + "/live"))
{
eveNames = lsFilesInDir(outputDir + "/events", ".html");
logNames = lsFilesInDir(outputDir + "/logs", "_log.html");
htmlText += "<h4>Logs</h4>\n";
htmlText += "<ul>\n";
for (auto &&dirName : dirNames)
for (auto &&logName : logNames)
{
htmlText += " <li><a href='" + dirName + "/index.html'>" + dirName + "</a></li>\n";
// name.substr(0, name.size() - 9) removes _log.html
auto name = logName.substr(0, logName.size() - 9);
htmlText += " <li><a href='logs/" + logName + "'>" + name + "</a></li>\n";
}
htmlText += "</ul>\n";
}
if (exists(outputDir + "/VIDEO_TS"))
{
htmlText += "<h4>Live</h4>\n";
htmlText += "<ul>\n";
htmlText += " <li><a href='stream.html'>" + share->camName + ":live" + "</a></li>\n";
@ -52,35 +55,24 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "<h4>Motion Events</h4>\n";
genHTMLstream("stream");
}
for (auto &&regName : regNames)
{
if (regName.ends_with("_log.html"))
{
logNames.push_back(regName);
}
else if (regName.ends_with(".html") &&
!regName.ends_with("index.html") &&
!regName.ends_with("stream.html") &&
!regName.ends_with("_log_lines.html"))
for (auto &&eveName : eveNames)
{
// regName.substr(0, regName.size() - 5) removes .html
auto name = regName.substr(0, regName.size() - 5);
auto name = eveName.substr(0, eveName.size() - 5);
htmlText += "<a href='" + regName + "'><img src='" + name + ".jpg" + "' style='width:25%;height:25%;'</a>\n";
htmlText += "<a href='events/" + eveName + "'><img src='events/" + name + ".jpg" + "' style='width:25%;height:25%;'</a>\n";
}
}
if (!logNames.empty())
else
{
htmlText += "<h4>Logs</h4>\n";
dirNames = lsDirsInDir(outputDir);
htmlText += "<ul>\n";
for (auto &&name : logNames)
for (auto &&dirName : dirNames)
{
// name.substr(0, name.size() - 9) removes _log.html
htmlText += " <li><a href='" + name + "'>" + name.substr(0, name.size() - 9) + "</a></li>\n";
htmlText += " <li><a href='" + dirName + "/index.html'>" + dirName + "</a></li>\n";
}
htmlText += "</ul>\n";
@ -119,7 +111,7 @@ void genHTMLstream(const string &name)
htmlText += " var hls = new Hls({\n";
htmlText += " debug: true,\n";
htmlText += " });\n";
htmlText += " hls.loadSource('stream.m3u8');\n";
htmlText += " hls.loadSource('" + name + ".m3u8');\n";
htmlText += " hls.attachMedia(video);\n";
htmlText += " hls.on(Hls.Events.MEDIA_ATTACHED, function () {\n";
htmlText += " video.muted = true;\n";
@ -127,7 +119,7 @@ void genHTMLstream(const string &name)
htmlText += " });\n";
htmlText += " }\n";
htmlText += " else if (video.canPlayType('application/vnd.apple.mpegurl')) {\n";
htmlText += " video.src = 'stream.m3u8';\n";
htmlText += " video.src = '" + name + ".m3u8';\n";
htmlText += " video.addEventListener('canplay', function () {\n";
htmlText += " video.play();\n";
htmlText += " });\n";
@ -162,7 +154,7 @@ void genHTMLvod(const string &name)
htmlText += "</body>\n";
htmlText += "</html>";
ofstream file(string(name + ".html").c_str());
ofstream file(string("events/" + name + ".html").c_str());
file << htmlText << endl;