v3.6.t1
-updated the way the video clips from the live stream are named to simple unsigned int number increments. doing this makes the file names much more predictable and removes the need for filesystem monitoring.
This commit is contained in:
parent
88ea1086f6
commit
ba54b5b44a
|
@ -92,7 +92,7 @@ void enforceMaxImages(shared_t *share)
|
||||||
{
|
{
|
||||||
auto names = lsFilesInDir(share->buffPath + "/img", ".bmp");
|
auto names = lsFilesInDir(share->buffPath + "/img", ".bmp");
|
||||||
|
|
||||||
while (names.size() > MAX_IMAGES)
|
while (names.size() > (share->liveSecs / 2))
|
||||||
{
|
{
|
||||||
QFile::remove(share->buffPath + "/img/" + names[0]);
|
QFile::remove(share->buffPath + "/img/" + names[0]);
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ void enforceMaxClips(shared_t *share)
|
||||||
{
|
{
|
||||||
auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt);
|
auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt);
|
||||||
|
|
||||||
while (names.size() > (share->liveSecs / SEG_SIZE))
|
while (names.size() > (share->liveSecs / 2))
|
||||||
{
|
{
|
||||||
QFile::remove(share->buffPath + "/live/" + names[0]);
|
QFile::remove(share->buffPath + "/live/" + names[0]);
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,8 @@ using namespace std;
|
||||||
#define APP_NAME "JustMotion"
|
#define APP_NAME "JustMotion"
|
||||||
#define APP_TARGET "jmotion"
|
#define APP_TARGET "jmotion"
|
||||||
#define DATETIME_FMT "yyyyMMddhhmmss"
|
#define DATETIME_FMT "yyyyMMddhhmmss"
|
||||||
#define STRFTIME_FMT "%Y%m%d%H%M%S"
|
|
||||||
#define PREV_IMG "&prev&"
|
#define PREV_IMG "&prev&"
|
||||||
#define NEXT_IMG "&next&"
|
#define NEXT_IMG "&next&"
|
||||||
#define SEG_SIZE 10
|
|
||||||
#define MAX_IMAGES 100
|
|
||||||
|
|
||||||
enum CmdExeType
|
enum CmdExeType
|
||||||
{
|
{
|
||||||
|
@ -76,6 +73,7 @@ struct shared_t
|
||||||
QString thumbExt;
|
QString thumbExt;
|
||||||
QString recScale;
|
QString recScale;
|
||||||
QString imgScale;
|
QString imgScale;
|
||||||
|
quint64 seed;
|
||||||
bool singleTenant;
|
bool singleTenant;
|
||||||
bool skipCmd;
|
bool skipCmd;
|
||||||
int liveSecs;
|
int liveSecs;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
pcTimer = 0;
|
pcTimer = 0;
|
||||||
|
deTimer = 0;
|
||||||
shared = sharedRes;
|
shared = sharedRes;
|
||||||
|
|
||||||
connect(thr, &QThread::started, this, &DetectLoop::init);
|
connect(thr, &QThread::started, this, &DetectLoop::init);
|
||||||
|
@ -26,21 +27,15 @@ DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QOb
|
||||||
void DetectLoop::init()
|
void DetectLoop::init()
|
||||||
{
|
{
|
||||||
pcTimer = new QTimer(this);
|
pcTimer = new QTimer(this);
|
||||||
evTimer = new QTimer(this);
|
deTimer = new QTimer(this);
|
||||||
dTime = QDateTime::currentDateTimeUtc();
|
|
||||||
|
|
||||||
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
|
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
|
||||||
connect(evTimer, &QTimer::timeout, this, &DetectLoop::evBreak);
|
connect(deTimer, &QTimer::timeout, this, &DetectLoop::preExec);
|
||||||
connect(this, &DetectLoop::loop, this, &DetectLoop::exec);
|
|
||||||
|
|
||||||
pcTimer->start(shared->postSecs * 1000);
|
pcTimer->start(shared->postSecs * 1000);
|
||||||
evTimer->start(shared->evMaxSecs * 1000);
|
deTimer->start(2000);
|
||||||
|
|
||||||
setupBuffDir(shared->buffPath);
|
setupBuffDir(shared->buffPath);
|
||||||
|
|
||||||
eTimer.start();
|
|
||||||
|
|
||||||
QTimer::singleShot(SEG_SIZE + 4, this, &DetectLoop::exec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DetectLoop::reset()
|
void DetectLoop::reset()
|
||||||
|
@ -54,6 +49,19 @@ void DetectLoop::reset()
|
||||||
eventQue.timeStamp.clear();
|
eventQue.timeStamp.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DetectLoop::preExec()
|
||||||
|
{
|
||||||
|
vidAPath = shared->buffPath + "/live/" + QString::number(shared->seed - 2).leftJustified(21, '0') + shared->streamExt;
|
||||||
|
vidBPath = shared->buffPath + "/live/" + QString::number(shared->seed - 1).leftJustified(21, '0') + shared->streamExt;
|
||||||
|
|
||||||
|
eTimer.start();
|
||||||
|
|
||||||
|
if (QFileInfo::exists(vidAPath) && QFileInfo::exists(vidBPath))
|
||||||
|
{
|
||||||
|
exec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DetectLoop::pcBreak()
|
void DetectLoop::pcBreak()
|
||||||
{
|
{
|
||||||
prevClips.clear();
|
prevClips.clear();
|
||||||
|
@ -84,16 +92,6 @@ void DetectLoop::pcBreak()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DetectLoop::evBreak()
|
|
||||||
{
|
|
||||||
if (eventQue.inQue)
|
|
||||||
{
|
|
||||||
shared->recList.append(eventQue);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
||||||
{
|
{
|
||||||
QString strLine(line);
|
QString strLine(line);
|
||||||
|
@ -136,9 +134,26 @@ QStringList DetectLoop::buildArgs(const QString &prev, const QString &next)
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgPath)
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
|
||||||
|
ret.append("-hide_banner");
|
||||||
|
ret.append("-loglevel");
|
||||||
|
ret.append("panic");
|
||||||
|
ret.append("-y");
|
||||||
|
ret.append("-i");
|
||||||
|
ret.append(vidSrc);
|
||||||
|
ret.append("-frames:v");
|
||||||
|
ret.append("1");
|
||||||
|
ret.append(imgPath);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
QString DetectLoop::statusLine()
|
QString DetectLoop::statusLine()
|
||||||
{
|
{
|
||||||
if (eTimer.elapsed() >= 30000)
|
if (eTimer.elapsed() >= 5000)
|
||||||
{
|
{
|
||||||
emit starving();
|
emit starving();
|
||||||
|
|
||||||
|
@ -152,63 +167,25 @@ QString DetectLoop::statusLine()
|
||||||
|
|
||||||
void DetectLoop::exec()
|
void DetectLoop::exec()
|
||||||
{
|
{
|
||||||
auto vidPath = shared->buffPath + "/live/" + dTime.toString(DATETIME_FMT) + shared->streamExt;
|
auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp";
|
||||||
auto found = false;
|
auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp";
|
||||||
|
auto snapArgsA = buildSnapArgs(vidAPath, imgAPath);
|
||||||
|
auto snapArgsB = buildSnapArgs(vidBPath, imgBPath);
|
||||||
|
auto compArgs = buildArgs(imgAPath, imgBPath);
|
||||||
|
|
||||||
for (auto i = 0; i < 100; ++i)
|
if (compArgs.isEmpty())
|
||||||
{
|
{
|
||||||
if (QFileInfo::exists(vidPath))
|
qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
|
||||||
{
|
|
||||||
found = true; break;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dTime = dTime.addSecs(1);
|
QProcess::execute("ffmpeg", snapArgsA);
|
||||||
|
QProcess::execute("ffmpeg", snapArgsB);
|
||||||
|
|
||||||
vidPath = shared->buffPath + "/live/" + dTime.toString(DATETIME_FMT) + shared->streamExt;
|
if (QFile::exists(imgAPath) && QFile::exists(imgBPath))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
{
|
{
|
||||||
auto imgPath = shared->buffPath + "/img/" + QFileInfo(vidPath).baseName() + "-%03d" + ".bmp";
|
|
||||||
|
|
||||||
QProcess ffmpeg;
|
|
||||||
QStringList args;
|
|
||||||
QString imgA;
|
|
||||||
QString imgB;
|
|
||||||
|
|
||||||
args.append("ffmpeg");
|
|
||||||
args.append("-hide_banner");
|
|
||||||
//args.append("-loglevel");
|
|
||||||
//args.append("panic");
|
|
||||||
args.append("-y");
|
|
||||||
args.append("-i");
|
|
||||||
args.append(vidPath);
|
|
||||||
args.append("-vf");
|
|
||||||
args.append("fps=1/1");
|
|
||||||
args.append(imgPath);
|
|
||||||
|
|
||||||
ffmpeg.start(args[0], args.mid(1));
|
|
||||||
ffmpeg.waitForFinished();
|
|
||||||
|
|
||||||
for (auto i = 1; i <= SEG_SIZE; ++i)
|
|
||||||
{
|
|
||||||
auto imgNum = QString::number(i).rightJustified(3, '0');
|
|
||||||
auto imgPath = shared->buffPath + "/img/" + QFileInfo(vidPath).baseName() + "-" + imgNum + ".bmp";
|
|
||||||
|
|
||||||
if (QFileInfo::exists(imgPath))
|
|
||||||
{
|
|
||||||
if (imgA.isEmpty()) imgA = imgPath;
|
|
||||||
else if (imgB.isEmpty()) imgB = imgPath;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
eTimer.restart();
|
|
||||||
|
|
||||||
QProcess extComp;
|
QProcess extComp;
|
||||||
|
|
||||||
auto compArgs = buildArgs(imgA, imgB);
|
|
||||||
|
|
||||||
extComp.start(compArgs[0], compArgs.mid(1));
|
extComp.start(compArgs[0], compArgs.mid(1));
|
||||||
extComp.waitForFinished();
|
extComp.waitForFinished();
|
||||||
|
|
||||||
|
@ -225,38 +202,41 @@ void DetectLoop::exec()
|
||||||
|
|
||||||
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
|
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
|
||||||
|
|
||||||
if (score >= shared->imgThresh)
|
if (eventQue.inQue)
|
||||||
{
|
{
|
||||||
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
|
eventQue.queAge += 4;
|
||||||
|
|
||||||
if (eventQue.score < score)
|
if (eventQue.score < score)
|
||||||
{
|
{
|
||||||
eventQue.imgPath = imgB;
|
|
||||||
eventQue.score = score;
|
eventQue.score = score;
|
||||||
|
eventQue.imgPath = imgBPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventQue.vidList.append(vidAPath);
|
||||||
|
eventQue.vidList.append(vidBPath);
|
||||||
|
|
||||||
|
if (eventQue.queAge >= shared->evMaxSecs)
|
||||||
|
{
|
||||||
|
eventQue.inQue = false;
|
||||||
|
|
||||||
|
shared->recList.append(eventQue);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (score >= shared->imgThresh)
|
||||||
|
{
|
||||||
|
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
|
||||||
|
|
||||||
|
eventQue.score = score;
|
||||||
|
eventQue.imgPath = imgBPath;
|
||||||
eventQue.inQue = true;
|
eventQue.inQue = true;
|
||||||
eventQue.queAge = 0;
|
eventQue.queAge = 0;
|
||||||
eventQue.timeStamp = QDateTime::currentDateTimeUtc().toString(DATETIME_FMT);
|
eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT);
|
||||||
|
|
||||||
if (!eventQue.vidList.contains(vidPath))
|
eventQue.vidList.append(vidAPath);
|
||||||
{
|
eventQue.vidList.append(vidBPath);
|
||||||
eventQue.vidList.append(vidPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imgA.clear();
|
|
||||||
imgB.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit loop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QTimer::singleShot(SEG_SIZE + 4, this, &DetectLoop::exec);
|
|
||||||
|
|
||||||
dTime = QDateTime::currentDateTimeUtc();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,37 +23,34 @@ private:
|
||||||
|
|
||||||
QString vidAPath;
|
QString vidAPath;
|
||||||
QString vidBPath;
|
QString vidBPath;
|
||||||
QString vidAName;
|
|
||||||
QString vidBName;
|
|
||||||
QStringList prevClips;
|
QStringList prevClips;
|
||||||
QTimer *pcTimer;
|
QTimer *pcTimer;
|
||||||
QTimer *evTimer;
|
QTimer *deTimer;
|
||||||
QDateTime dTime;
|
|
||||||
QElapsedTimer eTimer;
|
QElapsedTimer eTimer;
|
||||||
evt_t eventQue;
|
evt_t eventQue;
|
||||||
shared_t *shared;
|
shared_t *shared;
|
||||||
|
|
||||||
float getFloatFromExe(const QByteArray &line);
|
float getFloatFromExe(const QByteArray &line);
|
||||||
QStringList buildArgs(const QString &prev, const QString &next);
|
QStringList buildArgs(const QString &prev, const QString &next);
|
||||||
|
QStringList buildSnapArgs(const QString &vidSrc, const QString &imgPath);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void reset();
|
void reset();
|
||||||
void pcBreak();
|
void pcBreak();
|
||||||
void evBreak();
|
void preExec();
|
||||||
void exec();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void exec();
|
||||||
QString statusLine();
|
QString statusLine();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
void starving();
|
void starving();
|
||||||
void loop();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DETECT_LOOP_H
|
#endif // DETECT_LOOP_H
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent)
|
RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent)
|
||||||
{
|
{
|
||||||
checkTimer = 0;
|
checkTimer = 0;
|
||||||
status = 0;
|
|
||||||
shared = sharedRes;
|
shared = sharedRes;
|
||||||
|
|
||||||
|
sharedRes->seed = 0;
|
||||||
|
|
||||||
connect(thr, &QThread::started, this, &RecordLoop::init);
|
connect(thr, &QThread::started, this, &RecordLoop::init);
|
||||||
connect(thr, &QThread::finished, this, &RecordLoop::deleteLater);
|
connect(thr, &QThread::finished, this, &RecordLoop::deleteLater);
|
||||||
|
|
||||||
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
|
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
|
||||||
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
|
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
|
||||||
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
|
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
|
||||||
connect(this, &RecordLoop::finished, this, &RecordLoop::segFinished);
|
|
||||||
|
|
||||||
moveToThread(thr);
|
moveToThread(thr);
|
||||||
}
|
}
|
||||||
|
@ -48,56 +48,38 @@ void RecordLoop::init()
|
||||||
restart();
|
restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordLoop::segFinished(int retCode)
|
QString RecordLoop::camCmdFromConf()
|
||||||
{
|
{
|
||||||
status = retCode;
|
auto ret = "ffmpeg -hide_banner -y -i '" + shared->recordUri + "' -strftime 1 -strftime_mkdir 1 ";
|
||||||
|
|
||||||
restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList RecordLoop::camCmdFromConf()
|
|
||||||
{
|
|
||||||
auto tobj = QDateTime::currentDateTimeUtc();
|
|
||||||
auto ret = QStringList();
|
|
||||||
|
|
||||||
ret.append("ffmpeg");
|
|
||||||
ret.append("-hide_banner");
|
|
||||||
ret.append("-y");
|
|
||||||
ret.append("-i");
|
|
||||||
ret.append(shared->recordUri);
|
|
||||||
|
|
||||||
if (shared->recordUri.contains("rtsp"))
|
if (shared->recordUri.contains("rtsp"))
|
||||||
{
|
{
|
||||||
ret.append("-rtsp_transport");
|
ret += "-rtsp_transport udp ";
|
||||||
ret.append("udp");
|
}
|
||||||
|
|
||||||
|
if (shared->seed == 18446744073709551615ULL)
|
||||||
|
{
|
||||||
|
setupBuffDir(shared->buffPath, true);
|
||||||
|
|
||||||
|
shared->seed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shared->vidCodec != "copy")
|
if (shared->vidCodec != "copy")
|
||||||
{
|
{
|
||||||
ret.append("-vf");
|
ret += "-vf fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale + " ";
|
||||||
ret.append("fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.append("-vcodec"); ret.append(shared->vidCodec);
|
ret += "-vcodec " + shared->vidCodec + " ";
|
||||||
ret.append("-acodec"); ret.append(shared->audCodec);
|
ret += "-acodec " + shared->audCodec + " ";
|
||||||
ret.append("-reset_timestamps");
|
ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames 'expr:gte(t, n_forced * 2)' -segment_time 2 -f segment ";
|
||||||
ret.append("1");
|
ret += shared->buffPath + "/live/" + QString::number(shared->seed++).leftJustified(21, '0') + shared->streamExt;
|
||||||
ret.append("-sc_threshold");
|
|
||||||
ret.append("0");
|
|
||||||
ret.append("-g");
|
|
||||||
ret.append("2");
|
|
||||||
ret.append("-force_key_frames");
|
|
||||||
ret.append("expr:gte(t, n_forced * 2)");
|
|
||||||
ret.append("-t");
|
|
||||||
ret.append(QString::number(SEG_SIZE));
|
|
||||||
ret.append(shared->buffPath + "/live/" + tobj.toString(DATETIME_FMT) + shared->streamExt);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString RecordLoop::statusLine()
|
QString RecordLoop::statusLine()
|
||||||
{
|
{
|
||||||
if ((status == 0) || (status == 255))
|
if (state() == QProcess::Running)
|
||||||
{
|
{
|
||||||
return "OK ";
|
return "OK ";
|
||||||
}
|
}
|
||||||
|
@ -117,13 +99,20 @@ void RecordLoop::restart()
|
||||||
if (state() == QProcess::Running)
|
if (state() == QProcess::Running)
|
||||||
{
|
{
|
||||||
terminate();
|
terminate();
|
||||||
|
waitForFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cmdLine = camCmdFromConf();
|
||||||
|
auto args = parseArgs(cmdLine.toUtf8(), -1);
|
||||||
|
|
||||||
|
qInfo() << "start recording command: " << cmdLine;
|
||||||
|
|
||||||
|
if (args.isEmpty())
|
||||||
|
{
|
||||||
|
qCritical() << "err: couldn't parse a program name";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resetTime();
|
|
||||||
|
|
||||||
auto args = camCmdFromConf();
|
|
||||||
|
|
||||||
setProgram(args[0]);
|
setProgram(args[0]);
|
||||||
setArguments(args.mid(1));
|
setArguments(args.mid(1));
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,12 @@ private:
|
||||||
|
|
||||||
shared_t *shared;
|
shared_t *shared;
|
||||||
QTimer *checkTimer;
|
QTimer *checkTimer;
|
||||||
int status;
|
|
||||||
|
|
||||||
QStringList camCmdFromConf();
|
QString camCmdFromConf();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void restart();
|
void restart();
|
||||||
void segFinished(int retCode);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user