-pixel sizes for video and thumbnails are now configurable
-the amount of recording and snapshot threads are now
 configurable
-changed the default buffer and recording directories to
 drop the application name and also drop a level
-service files are now stored in a dedicated dir
-fixed logging so it will capture errors better
-moved folder structure building to rdConfig() and added more
 error capturing
-recording fps is now configurable
This commit is contained in:
Zii 2023-10-19 15:04:39 -04:00
parent 41ccf1d1e7
commit 83080cfe41
7 changed files with 200 additions and 122 deletions

View File

@ -1,7 +1,9 @@
#!/bin/sh
export DEBIAN_FRONTEND=noninteractive
if ! command -v magick &> /dev/null
magick -version &> /dev/null
if [ ! $? -eq 0 ]
then
apt install -y git
git clone https://github.com/ImageMagick/ImageMagick.git .build-imagemagick

View File

@ -7,20 +7,20 @@ if [ ! -d "/opt/mow" ]; then
mkdir /opt/mow
fi
if [ ! -d "/var/opt/mow" ]; then
mkdir /var/opt/mow
if [ ! -d "/var/footage" ]; then
mkdir /var/footage
fi
if [ ! -d "/etc/mow" ]; then
mkdir /etc/mow
fi
if [ ! -d "/var/opt/mow/buf" ]; then
mkdir /var/opt/mow/buf
if [ ! -d "/var/buffer" ]; then
mkdir /var/buffer
fi
if [ ! -d "/var/opt/mow/rec" ]; then
mkdir /var/opt/mow/rec
if [ ! -d "/var/mow_serv" ]; then
mkdir /var/mow_serv
fi
cp -v ./.build-mow/mow /opt/mow/bin
@ -36,12 +36,13 @@ printf "rm -v /opt/mow/run\n" >> /opt/mow/uninst
printf "rm -v /opt/mow/uninst\n" >> /opt/mow/uninst
printf "rm -v /usr/bin/mow\n" >> /opt/mow/uninst
printf "rm -rv /opt/mow\n" >> /opt/mow/uninst
printf "rm -r /var/opt/mow/buf\n" >> /opt/mow/uninst
printf "deluser mow\n" >> /opt/mow/uninst
useradd -r mow
chown -R mow:mow /var/opt/mow
chown -R -v mow:mow /var/footage
chown -R -v mow:mow /var/buffer
chown -R -v mow:mow /var/mow_serv
chmod -v +x /opt/mow/run
chmod -v +x /opt/mow/bin

View File

@ -2,5 +2,25 @@
export DEBIAN_FRONTEND=noninteractive
apt update -y
apt install -y pkg-config cmake make g++
apt install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev x264 libx264-dev libilmbase-dev qt6-base-dev qtchooser qmake6 qt6-base-dev-tools libxkbcommon-dev libfuse-dev fuse3
sh imgmagick_build.sh
if [ $? -eq 0 ]
then
apt install -y ffmpeg
apt install -y libavcodec-dev
apt install -y libavformat-dev
apt install -y libavutil-dev
apt install -y libswscale-dev
apt install -y x264
apt install -y libx264-dev
apt install -y libilmbase-dev
apt install -y qt6-base-dev
apt install -y qtchooser
apt install -y qmake6
apt install -y qt6-base-dev-tools
apt install -y libxkbcommon-dev
apt install -y libfuse-dev
apt install -y fuse3
sh imgmagick_build.sh
fi

View File

@ -14,38 +14,9 @@
Camera::Camera(QObject *parent) : QObject(parent) {}
void Camera::cleanup()
{
if (QFileInfo::exists(shared.tmpDir + "/rec"))
{
QProcess::execute("rm", {shared.tmpDir + "/rec"});
}
}
int Camera::start(const QStringList &args)
{
if (rdConf(getParam("-c", args), &shared))
{
QProcess::execute("mkdir", {"-p", shared.outDir});
QProcess::execute("mkdir", {"-p", shared.tmpDir});
QProcess::execute("mkdir", {"-p", shared.tmpDir + "/live"});
QProcess::execute("mkdir", {"-p", shared.tmpDir + "/logs"});
QProcess::execute("mkdir", {"-p", shared.tmpDir + "/img"});
cleanup();
touch(shared.tmpDir + "/stream.m3u8");
QProcess::execute("ln", {"-s", shared.outDir, shared.tmpDir + "/rec"});
if (!QDir::setCurrent(shared.tmpDir))
{
QTextStream(stderr) << "err: failed to change/create the current working directory to camera folder: '" << shared.outDir << "' does it exists?" << Qt::endl;
shared.retCode = ENOENT;
}
else
{
auto thr1 = new QThread(nullptr);
auto thr2 = new QThread(nullptr);
@ -62,7 +33,6 @@ int Camera::start(const QStringList &args)
thr3->start();
thr4->start();
}
}
return shared.retCode;
}
@ -131,10 +101,14 @@ void RecLoop::updateCmd()
QStringList recArgs;
QStringList imgArgs;
recArgs << "-c";
recArgs << buildThreadCount(shared->recThreads);
recArgs << "ffmpeg";
recArgs << "-hide_banner";
recArgs << "-i" << shared->recordUri;
recArgs << "-strftime" << "1";
recArgs << "-strftime_mkdir" << "1";
recArgs << "-vf" << "fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale;
recArgs << "-hls_segment_filename" << "live/" + QString(STRFTIME_FMT) + shared->streamExt;
recArgs << "-y";
recArgs << "-vcodec" << shared->streamCodec;
@ -142,23 +116,38 @@ void RecLoop::updateCmd()
recArgs << "-hls_time" << "2";
recArgs << "-hls_list_size" << "1000";
recArgs << "-hls_flags" << "append_list+omit_endlist";
if (shared->recordUri.contains("rtsp"))
{
recArgs << "-rtsp_transport" << "tcp";
}
recArgs << "-t" << QString::number(heartBeat);
recArgs << "stream.m3u8";
imgArgs << "-c";
imgArgs << buildThreadCount(shared->imgThreads);
imgArgs << "ffmpeg";
imgArgs << "-hide_banner";
imgArgs << "-i" << shared->recordUri;
imgArgs << "-strftime" << "1";
imgArgs << "-strftime_mkdir" << "1";
imgArgs << "-vf" << "fps=1,scale=320:240";
imgArgs << "-vf" << "fps=1,scale=" + shared->imgScale;
if (shared->recordUri.contains("rtsp"))
{
imgArgs << "-rtsp_transport" << "tcp";
}
imgArgs << "-t" << QString::number(heartBeat);
imgArgs << "img/" + QString(STRFTIME_FMT) + ".bmp";
recProc->setProgram("ffmpeg");
recProc->setWorkingDirectory(shared->tmpDir);
recProc->setProgram("taskset");
recProc->setArguments(recArgs);
imgProc->setProgram("ffmpeg");
imgProc->setWorkingDirectory(shared->tmpDir);
imgProc->setProgram("taskset");
imgProc->setArguments(imgArgs);
recLog("rec_args: " + recArgs.join(" "), shared);
@ -182,8 +171,6 @@ void RecLoop::term()
void RecLoop::procError(const QString &desc, QProcess *proc)
{
if (proc->isOpen() && (proc->state() != QProcess::Running))
{
auto errBlob = QString(proc->readAllStandardError());
auto errLines = errBlob.split('\n');
@ -194,7 +181,6 @@ void RecLoop::procError(const QString &desc, QProcess *proc)
recLog(desc + "_cmd_stderr: " + line, shared);
}
}
}
}
bool RecLoop::exec()
@ -216,11 +202,11 @@ Upkeep::Upkeep(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(shared
bool Upkeep::exec()
{
enforceMaxLogSize(QString("logs/") + REC_LOG_NAME, shared);
enforceMaxLogSize(QString("logs/") + DET_LOG_NAME, shared);
enforceMaxLogSize(shared->tmpDir + "/logs/" + REC_LOG_NAME, shared);
enforceMaxLogSize(shared->tmpDir + "/logs/" + DET_LOG_NAME, shared);
dumpLogs(QString("logs/") + REC_LOG_NAME, shared->recLog);
dumpLogs(QString("logs/") + DET_LOG_NAME, shared->detLog);
dumpLogs(shared->tmpDir + "/logs/" + REC_LOG_NAME, shared->recLog);
dumpLogs(shared->tmpDir + "/logs/" + DET_LOG_NAME, shared->detLog);
shared->logMutex.lock();
shared->recLog.clear();
@ -228,7 +214,7 @@ bool Upkeep::exec()
shared->logMutex.unlock();
enforceMaxEvents(shared);
enforceMaxImages();
enforceMaxImages(shared);
enforceMaxVids(shared);
return Loop::exec();
@ -258,7 +244,7 @@ bool EventLoop::exec()
args << "convert";
args << imgPath;
args << "rec/" + name + shared->thumbExt;
args << shared->outDir + "/" + name + shared->thumbExt;
proc.start("magick", args);
proc.waitForFinished();
@ -291,8 +277,8 @@ bool EventLoop::exec()
auto maxSecs = shared->evMaxSecs / 2;
// half the maxsecs value to get front-back half secs
auto backFiles = backwardFacingFiles("live", shared->streamExt, event->timeStamp, maxSecs);
auto frontFiles = forwardFacingFiles("live", shared->streamExt, event->timeStamp, maxSecs);
auto backFiles = backwardFacingFiles(shared->tmpDir + "/live", shared->streamExt, event->timeStamp, maxSecs);
auto frontFiles = forwardFacingFiles(shared->tmpDir + "/live", shared->streamExt, event->timeStamp, maxSecs);
vidList.append(backFiles + frontFiles);
rmIndx.append(i);
@ -351,7 +337,7 @@ bool EventLoop::wrOutVod()
args << "-safe" << "0";
args << "-i" << concat;
args << "-c" << "copy";
args << "rec/" + name + shared->recExt;
args << shared->outDir + "/" + name + shared->recExt;
proc.setProgram("ffmpeg");
proc.setArguments(args);

View File

@ -24,8 +24,6 @@ private:
shared_t shared;
void cleanup();
public:
explicit Camera(QObject *parent = nullptr);

View File

@ -107,31 +107,31 @@ QStringList forwardFacingFiles(const QString &path, const QString &ext, const QD
void enforceMaxEvents(shared_t *share)
{
auto names = lsFilesInDir("events", ".mp4");
auto names = lsFilesInDir(share->outDir, share->recExt);
while (names.size() > share->maxEvents)
{
auto nameOnly = "events/" + names[0];
auto nameOnly = share->outDir + "/" + names[0];
nameOnly.remove(".mp4");
nameOnly.chop(share->recExt.size());
auto mp4File = nameOnly + ".mp4";
auto imgFile = nameOnly + ".jpg";
auto vidFile = nameOnly + share->recExt;
auto imgFile = nameOnly + share->thumbExt;
QFile::remove(mp4File);
QFile::remove(vidFile);
QFile::remove(imgFile);
names.removeFirst();
}
}
void enforceMaxImages()
void enforceMaxImages(shared_t *share)
{
auto names = lsFilesInDir("img", ".bmp");
auto names = lsFilesInDir(share->tmpDir + "/img", ".bmp");
while (names.size() > MAX_IMAGES)
{
QFile::remove("img/" + names[0]);
QFile::remove(share->tmpDir + "/img/" + names[0]);
names.removeFirst();
}
@ -139,11 +139,11 @@ void enforceMaxImages()
void enforceMaxVids(shared_t *share)
{
auto names = lsFilesInDir("live", share->streamExt);
auto names = lsFilesInDir(share->tmpDir + "/live", share->streamExt);
while (names.size() > MAX_VIDEOS)
{
QFile::remove("live/" + names[0]);
QFile::remove(share->tmpDir + "/live/" + names[0]);
names.removeFirst();
}
@ -169,22 +169,9 @@ void rdLine(const QString &param, const QString &line, bool *value)
{
if (line.startsWith(param))
{
*value = (line == "y" || line == "Y");
}
}
auto val = line.mid(param.size()).trimmed();
void touch(const QString &path)
{
if (!QFile::exists(path))
{
QFile file(path);
if (file.open(QFile::WriteOnly))
{
file.write("");
}
file.close();
*value = (val == "y" || val == "Y");
}
}
@ -196,6 +183,18 @@ void extCorrection(QString &ext)
}
}
bool mkPath(const QString &path)
{
auto ret = true;
if (!QDir().exists(path))
{
ret = QDir().mkpath(path);
}
return ret;
}
bool rdConf(const QString &filePath, shared_t *share)
{
QFile varFile(filePath);
@ -204,7 +203,7 @@ bool rdConf(const QString &filePath, shared_t *share)
{
share->retCode = ENOENT;
QTextStream(stderr) << "err: config file: " << filePath << " does not exists or lack read permissions." << Qt::endl;
QTextStream(stderr) << "err: config file - " << filePath << " does not exists or lack read permissions." << Qt::endl;
}
else
{
@ -212,6 +211,8 @@ bool rdConf(const QString &filePath, shared_t *share)
share->postCmd.clear();
share->camName.clear();
auto thrCount = QThread::idealThreadCount() / 2;
share->retCode = 0;
share->imgThresh = 8000;
share->maxEvents = 100;
@ -220,8 +221,8 @@ bool rdConf(const QString &filePath, shared_t *share)
share->postSecs = 60;
share->evMaxSecs = 30;
share->conf = filePath;
share->buffPath = "/var/opt/" + QString(APP_BIN) + "/buf";
share->recRoot = "/var/opt/" + QString(APP_BIN) + "/rec";
share->buffPath = "/var/buffer";
share->recPath = "/var/footage";
share->outputType = "stderr";
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
share->streamCodec = "copy";
@ -229,6 +230,11 @@ bool rdConf(const QString &filePath, shared_t *share)
share->recExt = ".mp4";
share->thumbExt = ".jpg";
share->singleTenant = false;
share->imgThreads = thrCount;
share->recThreads = thrCount;
share->recFps = 30;
share->recScale = "1280:720";
share->imgScale = "320:240";
QString line;
@ -241,7 +247,7 @@ bool rdConf(const QString &filePath, shared_t *share)
rdLine("cam_name = ", line, &share->camName);
rdLine("recording_uri = ", line, &share->recordUri);
rdLine("buffer_path = ", line, &share->buffPath);
rdLine("rec_root = ", line, &share->recRoot);
rdLine("rec_path = ", line, &share->recPath);
rdLine("max_event_secs = ", line, &share->evMaxSecs);
rdLine("post_secs = ", line, &share->postSecs);
rdLine("post_cmd = ", line, &share->postCmd);
@ -255,6 +261,11 @@ bool rdConf(const QString &filePath, shared_t *share)
rdLine("rec_ext = ", line, &share->recExt);
rdLine("thumbnail_ext = ", line, &share->thumbExt);
rdLine("single_tenant = ", line, &share->singleTenant);
rdLine("img_threads = ", line, &share->imgThreads);
rdLine("rec_threads = ", line, &share->recThreads);
rdLine("rec_fps = ", line, &share->recFps);
rdLine("rec_scale = ", line, &share->recScale);
rdLine("img_scale = ", line, &share->imgScale);
}
} while(!line.isEmpty());
@ -270,16 +281,56 @@ bool rdConf(const QString &filePath, shared_t *share)
if (share->singleTenant)
{
share->outDir = QDir().cleanPath(share->recRoot);
share->outDir = QDir().cleanPath(share->recPath);
share->tmpDir = QDir().cleanPath(share->buffPath);
}
else
{
share->outDir = QDir().cleanPath(share->recRoot) + "/" + share->camName;
share->outDir = QDir().cleanPath(share->recPath) + "/" + share->camName;
share->tmpDir = QDir().cleanPath(share->buffPath) + "/" + share->camName;
}
share->servPath = QString("/var/opt/") + APP_BIN + "/" + APP_BIN + "." + share->camName + ".service";
auto servDir = QString("/var/") + APP_BIN + QString("_serv");
share->retCode = EACCES;
if (!mkPath(servDir))
{
QTextStream(stderr) << "err: failed to create service directory - " << servDir << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->recPath))
{
QTextStream(stderr) << "err: failed to create root recording directory - " << share->recPath << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->buffPath))
{
QTextStream(stderr) << "err: failed to create root buffer directory - " << share->buffPath << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->outDir))
{
QTextStream(stderr) << "err: failed to create recording directory - " << share->outDir << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->tmpDir))
{
QTextStream(stderr) << "err: failed to create buffer directory - " << share->tmpDir << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->tmpDir + "/live"))
{
QTextStream(stderr) << "err: failed to create 'live' in the buffer directory - " << share->tmpDir << "/live" << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->tmpDir + "/logs"))
{
QTextStream(stderr) << "err: failed to create 'logs' in the buffer directory - " << share->tmpDir << "/logs" << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->tmpDir + "/img"))
{
QTextStream(stderr) << "err: failed to create 'img' in the buffer directory - " << share->tmpDir << "/img" << " check for write permissions." << Qt::endl;
}
else
{
share->retCode = 0;
share->servPath = QString("/var/") + APP_BIN + QString("_serv/") + APP_BIN + "." + share->camName + ".service";
}
}
return share->retCode == 0;
@ -287,7 +338,8 @@ bool rdConf(const QString &filePath, shared_t *share)
void rmServices()
{
auto files = lsFilesInDir(QString("/var/opt/") + APP_BIN, ".service");
auto path = QString("/var/") + APP_BIN + QString("_serv");
auto files = lsFilesInDir(path, ".service");
for (auto &&serv : files)
{
@ -295,7 +347,7 @@ void rmServices()
QProcess::execute("systemctl", {"disable", serv});
QFile::remove(QString("/lib/systemd/system/") + serv);
QFile::remove(QString("/var/opt/") + APP_BIN + "/" + serv);
QFile::remove(path + "/" + serv);
}
QProcess::execute("systemctl", {"daemon-reload"});
@ -303,7 +355,8 @@ void rmServices()
void listServices()
{
auto files = lsFilesInDir(QString("/var/opt/") + APP_BIN, ".service");
auto path = QString("/var/") + APP_BIN + QString("_serv");
auto files = lsFilesInDir(path, ".service");
for (auto &&serv : files)
{
@ -398,6 +451,18 @@ int loadServices(const QStringList &args)
return ret;
}
QString buildThreadCount(int count)
{
QString ret = "0";
for (auto i = 1; i < count; ++i)
{
ret.append(","); ret.append(QString::number(i));
}
return ret;
}
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos)
{
QStringList ret;

View File

@ -29,7 +29,7 @@
using namespace std;
#define APP_VER "3.3.t1"
#define APP_VER "3.3.t2"
#define APP_NAME "Motion Watch"
#define APP_BIN "mow"
#define REC_LOG_NAME "rec.log"
@ -64,7 +64,7 @@ struct shared_t
QString buffPath;
QString postCmd;
QString camName;
QString recRoot;
QString recPath;
QString servPath;
QString outputType;
QString compCmd;
@ -72,8 +72,13 @@ struct shared_t
QString streamExt;
QString recExt;
QString thumbExt;
QString recScale;
QString imgScale;
bool singleTenant;
bool skipCmd;
int recFps;
int imgThreads;
int recThreads;
int evMaxSecs;
int postSecs;
int imgThresh;
@ -83,6 +88,7 @@ struct shared_t
};
QString getParam(const QString &key, const QStringList &args);
QString buildThreadCount(int count);
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
QStringList lsDirsInDir(const QString &path);
QStringList listFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs, char dir);
@ -90,15 +96,15 @@ QStringList backwardFacingFiles(const QString &path, const QString &ext, const Q
QStringList forwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs);
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
bool rdConf(const QString &filePath, shared_t *share);
bool mkPath(const QString &path);
int loadServices(const QStringList &args);
void listServices();
void rmServices();
void touch(const QString &path);
void rdLine(const QString &param, const QString &line, QString *value);
void rdLine(const QString &param, const QString &line, int *value);
void rdLine(const QString &param, const QString &line, bool *value);
void enforceMaxEvents(shared_t *share);
void enforceMaxImages();
void enforceMaxImages(shared_t *share);
void enforceMaxVids(shared_t *share);
void extCorrection(QString &ext);