v2.0.t13
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:
parent
93723bb7b1
commit
83b206c06c
|
@ -134,18 +134,19 @@ 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(imgFile)) remove(imgFile);
|
||||
if (exists(webFile)) remove(webFile);
|
||||
if (exists(mp4File)) remove(mp4File);
|
||||
if (exists(imgFile)) remove(imgFile);
|
||||
if (exists(webFile)) remove(webFile);
|
||||
|
||||
names.erase(names.begin());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
94
src/main.cpp
Executable file → Normal file
94
src/main.cpp
Executable file → Normal 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,50 +56,29 @@ void eventLoop(shared_t *share)
|
|||
|
||||
share->recList.erase(it);
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,28 +168,21 @@ int main(int argc, char** argv)
|
|||
}
|
||||
else
|
||||
{
|
||||
sharedRes.retCode = 0;
|
||||
sharedRes.skipCmd = false;
|
||||
sharedRes.postCmdRunning = false;
|
||||
sharedRes.retCode = 0;
|
||||
sharedRes.procTime = 0;
|
||||
sharedRes.skipCmd = 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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -85,8 +103,8 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
|
|||
|
||||
if (capture.isOpened())
|
||||
{
|
||||
Mat prev;
|
||||
Mat next;
|
||||
Mat prev;
|
||||
Mat next;
|
||||
|
||||
detLog("capture open successful.", 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
54
src/web.cpp
54
src/web.cpp
|
@ -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 &®Name : 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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user