Merge branch 'ZoneMinder:master' into janus

This commit is contained in:
Jonathan Bennett 2022-01-13 12:16:16 -06:00 committed by GitHub
commit 212d51f933
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 310 additions and 251 deletions

@ -1 +1 @@
Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f
Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878

View File

@ -5,7 +5,8 @@
Description=ZoneMinder CCTV recording and surveillance system
After=network.target mysql.service
# Remarked out so that it will start ZM on machines that don't have mysql installed
#Requires=mysql.service
# Override it by placing an override.conf in /etc/systemd/system/zoneminder.service.d
#BindsTo=mysql.service
[Service]
#User=www-data

View File

@ -273,6 +273,9 @@ sub zmDbDo {
if ( ! defined $rows ) {
$sql =~ s/\?/'%s'/;
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
} elsif ( ZoneMinder::Logger::logLevel() > INFO ) {
$sql =~ s/\?/'%s'/;
Debug(sprintf("Succeeded $sql : $rows rows affected", @_));
}
return $rows;
}

View File

@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL;
if ( ! ( $db_field =~ /\?/ ) ) {
if ( @{$$search{$k}} != 1 ) {
push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
} else {
push @where, $db_field.'=?';
push @where, '`'.$db_field.'`=?';
} # end if
} else {
$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
foreach my $p_k ( keys %{$$search{$k}} ) {
my $v = $$search{$k}{$p_k};
if ( ref $v eq 'ARRAY' ) {
push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')';
push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')';
push @values, $p_k, @{$v};
} else {
push @where, $db_field.'=?';
push @where, '`'.$db_field.'`=?';
push @values, $p_k, $v;
} # end if
} # end foreach p_k
@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
push @where, $db_field.' IS NULL';
} else {
if ( ! ( $db_field =~ /\?/ ) ) {
push @where, $db_field .'=?';
push @where, '`'.$db_field .'`=?';
} else {
push @where, $db_field;
}

View File

@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25;
@EXTRA_PERL_LIB@
use ZoneMinder;
use ZoneMinder::Monitor;
use ZoneMinder::Trigger::Channel::Inet;
use ZoneMinder::Trigger::Channel::Unix;
use ZoneMinder::Trigger::Channel::Serial;
@ -203,14 +204,10 @@ while (!$zm_terminate) {
# Check for alarms that might have happened
my @out_messages;
foreach my $monitor ( values %monitors ) {
if ($$monitor{Function} eq 'None') {
$monitor_reload_time = 0;
next;
}
if (!zmMemVerify($monitor)) {
if (!$monitor->connect()) {
# Our attempt to verify the memory handle failed. We should reload the monitors.
# Don't need to zmMemInvalidate because the monitor reload will do it.
Debug("Failed connect, putting on reloads");
push @needsReload, $monitor;
next;
}
@ -249,6 +246,7 @@ while (!$zm_terminate) {
}
$monitor->{LastState} = $state;
$monitor->{LastEvent} = $last_event;
$monitor->disconnect();
} # end foreach monitor
foreach my $connection ( @out_connections ) {
@ -298,15 +296,22 @@ while (!$zm_terminate) {
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) {
foreach my $monitor ( values(%monitors) ) {
zmMemInvalidate( $monitor ); # Free up any used memory handle
}
loadMonitors();
@needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
} elsif ( @needsReload ) {
foreach my $monitor ( @needsReload ) {
loadMonitor($monitor);
} elsif (@needsReload) {
foreach my $monitor (@needsReload) {
$monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id());
if ( $$monitor{Function} eq 'None' ) {
delete $monitors{$monitor->Id()};
} elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) {
delete $monitors{$monitor->Id()};
} else {
if ($monitor->connect()) {
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
}
}
@needsReload = ();
}
@ -317,40 +322,21 @@ while (!$zm_terminate) {
Info('Trigger daemon exiting');
exit;
sub loadMonitor {
my $monitor = shift;
Debug('Loading monitor '.$monitor);
zmMemInvalidate($monitor);
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
} # end sub loadMonitor
sub loadMonitors {
$monitor_reload_time = time();
my %new_monitors = ();
%monitors = ();
my $sql = 'SELECT * FROM `Monitors`
WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'.
( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
;
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal( "Can't execute: ".$sth->errstr() );
while ( my $monitor = $sth->fetchrow_hashref() ) {
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
foreach my $monitor ( ZoneMinder::Monitor->find(
Function=>['Modect','Mocord','Nodect','Record'],
($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()),
)) {
if ($monitor->connect()) { # This will re-init shared memory
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
$new_monitors{$monitor->{Id}} = $monitor;
$monitors{$monitor->{Id}} = $monitor;
} # end while fetchrow
%monitors = %new_monitors;
} # end sub loadMonitors
sub handleMessage {

View File

@ -2,7 +2,7 @@
#
# ==========================================================================
#
# ZoneMinder Update Script, $Date$, $Revision$
# ZoneMinder Update Script
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
@ -31,29 +31,30 @@ zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u <dbu
=head1 DESCRIPTION
This script just checks what the most recent release of ZoneMinder is
at the the moment. It will eventually be responsible for applying and
configuring upgrades etc, including on the fly upgrades.
This script checks what the most recent release of ZoneMinder is
at the the moment by downloading https://update.zoneminder.com/version.txt.
It can also apply and configure upgrades etc, including on the fly upgrades.
=head1 OPTIONS
-c, --check - Check for updated versions of ZoneMinder
-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi
--migrate-events - Update database structures as per USE_DEEP_STORAGE setting.
-v <version>, --version=<version> - Force upgrade to the current version from <version>
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
-d <dir>, --dir=<dir> - Directory containing update files if not in default build location
-interactive - interact with the user
-nointeractive - do not interact with the user
-c, --check - Check for updated versions of ZoneMinder.
If not interactive zmupdate.pl will stay running, checking every hour.
If interactive will try once, print out result and quit.
-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi
--migrate-events - Update database structures as per USE_DEEP_STORAGE setting.
-v <version>, --version=<version> - Force upgrade to the current version from <version>
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
-d <dir>, --dir=<dir> - Directory containing update files if not in default build location
-interactive - interact with the user
-nointeractive - do not interact with the user
=cut
use strict;
use warnings;
use bytes;
use version;
use Crypt::Eksblowfish::Bcrypt;
use Data::Entropy::Algorithms qw(rand_bits);
# ==========================================================================
#
@ -122,9 +123,8 @@ GetOptions(
) or pod2usage(-exitstatus => -1);
my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } );
if ( !$dbh ) {
die "Unable to connect to db\n";
}
die "Unable to connect to db\n" if !$dbh;
$Config{ZM_DB_USER} = $dbUser;
$Config{ZM_DB_PASS} = $dbPass;
# we escape dbpass with single quotes so that $ in the password has no effect, but dbpass could have a ' in it.
@ -144,8 +144,10 @@ if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0))
pod2usage(-exitstatus => -1);
}
if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
print('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n");
if ($check and ($Config{ZM_CHECK_FOR_UPDATES} or $interactive) ) {
if (!$interactive) {
Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n");
}
my $currVersion = $Config{ZM_DYN_CURR_VERSION};
my $lastVersion = $Config{ZM_DYN_LAST_VERSION};
@ -153,16 +155,14 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
if ( !$currVersion ) {
$currVersion = $Config{ZM_VERSION};
my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'";
my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($currVersion) or die("Can't execute: ".$sth->errstr());
$sth->finish();
zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion);
}
while ( 1 ) {
my $now = time();
if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) {
if ( !$interactive and $lastVersion and $lastCheck and (($now-$lastCheck) <= CHECK_INTERVAL) ) {
Debug("Not checking for updates since we already have less than " . CHECK_INTERVAL . " seconds ago.");
} else {
Info('Checking for updates');
use LWP::UserAgent;
@ -175,21 +175,18 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
my $res = $ua->request($req);
if ( $res->is_success ) {
$lastVersion = $res->content;
chomp($lastVersion);
my $latestVersion = $res->content;
chomp($latestVersion);
$lastCheck = $now;
Info('Got version: '.$lastVersion);
Info('Got version: '.$latestVersion);
my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'';
my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr());
my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr());
$lv_sth->finish();
my $lc_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'';
my $lc_sth = $dbh->prepare_cached($lc_sql) or die("Can't prepare '$lc_sql': ".$dbh->errstr());
my $lc_res = $lc_sth->execute($lastCheck) or die("Can't execute: ".$lc_sth->errstr());
$lc_sth->finish();
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion);
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck);
if ($interactive) {
print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n");
exit(0);
}
} else {
Error('Error check failed: \''.$res->status_line().'\'');
}
@ -1044,14 +1041,16 @@ sub patchDB {
} # end sub patchDB
sub migratePasswords {
print ("Migratings passwords, if any...\n");
use Crypt::Eksblowfish::Bcrypt;
use Data::Entropy::Algorithms qw(rand_bits);
print("Migratings passwords, if any...\n");
my $sql = 'SELECT * FROM `Users`';
my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die("Can't execute: ".$sth->errstr());
while( my $user = $sth->fetchrow_hashref() ) {
while ( my $user = $sth->fetchrow_hashref() ) {
my $scheme = substr($user->{Password}, 0, 1);
if ($scheme eq '*') {
print ('-->'.$user->{Username}." password will be migrated\n");
print('-->'.$user->{Username}." password will be migrated\n");
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
my $settings = '$2a$10$'.$salt;
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);

View File

@ -23,6 +23,14 @@ void AnalysisThread::Start() {
void AnalysisThread::Run() {
while (!(terminate_ or zm_terminate)) {
monitor_->Analyse();
// Some periodic updates are required for variable capturing framerate
if (!monitor_->Analyse()) {
if (!(terminate_ or zm_terminate)) {
// We only sleep when Analyse returns false because it is an error condition and we will spin like mad if it persists.
Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE);
Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count()));
std::this_thread::sleep_for(sleep_for);
}
}
}
}

View File

@ -65,7 +65,8 @@ Event::Event(
last_db_frame(0),
have_video_keyframe(false),
//scheme
save_jpegs(0)
save_jpegs(0),
terminate_(false)
{
std::string notes;
createNotes(notes);
@ -133,98 +134,22 @@ Event::Event(
);
id = zmDbDoInsert(sql);
if (!SetPath(storage)) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql);
if (result) {
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
if (!storage) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if (monitor->ServerId())
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql);
if (result) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
}
if (!storage) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
zmDbDo(sql);
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */
videoStore = new VideoStore(
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ),
( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ),
monitor );
if ( !videoStore->open() ) {
Warning("Failed to open videostore, turning on jpegs");
delete videoStore;
videoStore = nullptr;
if ( ! ( save_jpegs & 1 ) ) {
save_jpegs |= 1; // Turn on jpeg storage
sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id);
zmDbDo(sql);
}
} else {
std::string codec = videoStore->get_codec();
video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
}
} // end if GetOptVideoWriter
if (storage != monitor->getStorage())
delete storage;
thread_ = std::thread(&Event::Run, this);
}
Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
Debug(1, "Deleting event, calling stop");
Stop();
if (thread_.joinable()) {
// Should be. Issuing the stop and then getting the lock
Debug(1, "Joinable");
thread_.join();
} else {
Debug(1, "Not Joinable");
}
/* Close the video file */
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
if (videoStore != nullptr) {
Debug(4, "Deleting video store");
delete videoStore;
@ -377,6 +302,12 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
} // void Event::updateNotes(const StringSetMap &newNoteSetMap)
void Event::AddPacket(const std::shared_ptr<ZMPacket>&packet) {
std::unique_lock<std::mutex> lck(packet_queue_mutex);
packet_queue.push(packet);
packet_queue_condition.notify_one();
}
void Event::AddPacket_(const std::shared_ptr<ZMPacket>&packet) {
have_video_keyframe = have_video_keyframe ||
( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) &&
( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) );
@ -655,3 +586,112 @@ bool Event::SetPath(Storage *storage) {
} // deep storage or not
return true;
} // end bool Event::SetPath
void Event::Run() {
Storage *storage = monitor->getStorage();
if (!SetPath(storage)) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql);
if (result) {
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
if (!storage) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if (monitor->ServerId())
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql);
if (result) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
}
if (!storage) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
zmDbDo(sql);
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */
videoStore = new VideoStore(
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ),
( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ),
monitor );
if ( !videoStore->open() ) {
Warning("Failed to open videostore, turning on jpegs");
delete videoStore;
videoStore = nullptr;
if ( ! ( save_jpegs & 1 ) ) {
save_jpegs |= 1; // Turn on jpeg storage
zmDbDo(stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id));
}
} else {
std::string codec = videoStore->get_codec();
video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
}
} // end if GetOptVideoWriter
if (storage != monitor->getStorage())
delete storage;
std::unique_lock<std::mutex> lck(packet_queue_mutex);
// The idea is to process the queue no matter what so that all packets get processed.
// We only break if the queue is empty
while (true) {
if (!packet_queue.empty()) {
Debug(1, "adding packet");
this->AddPacket_(packet_queue.front());
packet_queue.pop();
} else {
if (terminate_ or zm_terminate) {
Debug(1, "terminating");
break;
}
Debug(1, "waiting");
packet_queue_condition.wait(lck);
Debug(1, "wakeing");
}
}
}

View File

@ -22,14 +22,21 @@
#include "zm_config.h"
#include "zm_define.h"
#include "zm_packet.h"
#include "zm_storage.h"
#include "zm_time.h"
#include "zm_utils.h"
#include "zm_zone.h"
#include <atomic>
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <thread>
class EventStream;
class Frame;
@ -98,6 +105,15 @@ class Event {
void createNotes(std::string &notes);
std::queue<std::shared_ptr<ZMPacket>> packet_queue;
std::mutex packet_queue_mutex;
std::condition_variable packet_queue_condition;
void Run();
std::atomic<bool> terminate_;
std::thread thread_;
public:
static bool OpenFrameSocket(int);
static bool ValidateFrameSocket(int);
@ -118,6 +134,7 @@ class Event {
SystemTimePoint EndTime() const { return end_time; }
void AddPacket(const std::shared_ptr<ZMPacket> &p);
void AddPacket_(const std::shared_ptr<ZMPacket> &p);
bool WritePacket(const std::shared_ptr<ZMPacket> &p);
bool SendFrameImage(const Image *image, bool alarm_frame=false);
bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const;
@ -130,6 +147,12 @@ class Event {
int score = 0,
Image *alarm_image = nullptr);
void Stop() {
terminate_ = true;
packet_queue_condition.notify_all();
}
bool Stopped() const { return terminate_; }
private:
void WriteDbFrames();
bool SetPath(Storage *storage);

View File

@ -1862,12 +1862,10 @@ bool Monitor::Analyse() {
// Need to guard around event creation/deletion from Reload()
std::lock_guard<std::mutex> lck(event_mutex);
Debug(3, "Have event lock");
// if we have been told to be OFF, then we are off and don't do any processing.
if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) {
Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state));
int score = 0;
// Ready means that we have captured the warmup # of frames
if (!Ready()) {
Debug(3, "Not ready?");
@ -1875,6 +1873,7 @@ bool Monitor::Analyse() {
return false;
}
int score = 0;
std::string cause;
Event::StringSetMap noteSetMap;
@ -1883,11 +1882,18 @@ bool Monitor::Analyse() {
score += 9;
Debug(1, "Triggered on ONVIF");
if (!event) {
Event::StringSet noteSet;
noteSet.insert("ONVIF2");
noteSetMap[MOTION_CAUSE] = noteSet;
event = openEvent(snap, "ONVIF", noteSetMap);
cause += "ONVIF";
} else {
event->addNote(MOTION_CAUSE, "ONVIF2");
// Add to cause
}
Event::StringSet noteSet;
noteSet.insert("ONVIF2");
noteSetMap[MOTION_CAUSE] = noteSet;
// Regardless of previous state, we go to ALARM
shared_data->state = state = ALARM;
//If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm.
if (!ONVIF_Closes_Event && state == ALARM)
ONVIF_Trigger_State = FALSE;
@ -1899,11 +1905,16 @@ bool Monitor::Analyse() {
score += trigger_data->trigger_score;
Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score);
if (!event) {
cause += trigger_data->trigger_cause;
Event::StringSet noteSet;
noteSet.insert(trigger_data->trigger_text);
noteSetMap[trigger_data->trigger_cause] = noteSet;
event = openEvent(snap, trigger_data->trigger_cause, noteSetMap);
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id());
} else {
event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text);
// Need to know if we should end the previous and start a new one, or just add the data
}
Event::StringSet noteSet;
noteSet.insert(trigger_data->trigger_text);
noteSetMap[trigger_data->trigger_cause] = noteSet;
shared_data->state = state = ALARM;
} // end if trigger_on
// FIXME this snap might not be the one that caused the signal change. Need to store that in the packet.
@ -1917,7 +1928,7 @@ bool Monitor::Analyse() {
}
} else if (function == MOCORD or function == RECORD) {
if (!event) {
if (cause.length()) cause += ", ";
if (!cause.empty()) cause += ", ";
cause += SIGNAL_CAUSE + std::string(": Reacquired");
} else {
event->addNote(SIGNAL_CAUSE, "Reacquired");
@ -1976,12 +1987,10 @@ bool Monitor::Analyse() {
packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all
packetqueue.wait();
// Everything may have changed, just return and start again. This needs to be more RAII
return false;
return true;
} // end while ! decoded
} // end if decoding enabled
SystemTimePoint timestamp = snap->timestamp;
if (Active() and (function == MODECT or function == MOCORD)) {
Debug(3, "signal and active and modect");
Event::StringSet zoneSet;
@ -1994,17 +2003,19 @@ bool Monitor::Analyse() {
}
if (snap->image) {
alarm_image.Assign(*(snap->image));
// decoder may not have been able to provide an image
if (!ref_image.Buffer()) {
Debug(1, "Assigning instead of Detecting");
ref_image.Assign(*(snap->image));
alarm_image.Assign(*(snap->image));
} else if (!(analysis_image_count % (motion_frame_skip+1))) {
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
// Get new score.
int motion_score = DetectMotion(*(snap->image), zoneSet);
snap->score = DetectMotion(*(snap->image), zoneSet);
if (!snap->analysis_image)
snap->analysis_image = new Image(*(snap->image));
// lets construct alarm cause. It will contain cause + names of zones alarmed
snap->zone_stats.reserve(zones.size());
for (const Zone &zone : zones) {
@ -2014,27 +2025,31 @@ bool Monitor::Analyse() {
if (zone.Alarmed()) {
if (!snap->alarm_cause.empty()) snap->alarm_cause += ",";
snap->alarm_cause += std::string(zone.Label());
if (zone.AlarmImage())
snap->analysis_image->Overlay(*(zone.AlarmImage()));
}
}
alarm_image.Assign(*(snap->analysis_image));
Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)",
score, last_motion_score, motion_score);
score, last_motion_score, snap->score);
motion_frame_count += 1;
last_motion_score = motion_score;
last_motion_score = snap->score;
if (motion_score) {
if (snap->score) {
if (cause.length()) cause += ", ";
cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
} else {
Debug(1, "Skipped motion detection last motion score was %d", last_motion_score);
alarm_image.Assign(*(snap->image));
}
} else {
Debug(1, "no image so skipping motion detection");
} // end if has image
score += last_motion_score;
//score += last_motion_score;
} else {
Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d",
Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d",
Active(), enabled, shared_data->active,
(function == MODECT or function == MOCORD)
);
@ -2045,17 +2060,17 @@ bool Monitor::Analyse() {
if (event) {
Debug(2, "Have event %" PRIu64 " in record", event->Id());
if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)
if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)
&& ((function == MOCORD && event_close_mode != CLOSE_TIME)
|| (function == RECORD && event_close_mode == CLOSE_TIME)
|| std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()) % section_length == Seconds(0))) {
|| std::chrono::duration_cast<Seconds>(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) {
Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 ,
name.c_str(),
image_count,
event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(event->StartTime().time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(section_length).count()));
closeEvent();
} // end if section_length
@ -2073,13 +2088,14 @@ bool Monitor::Analyse() {
} // end if ! event
} // end if RECORDING
if (score and (function != MONITOR)) {
if ((snap->score > 0) and (function != MONITOR)) {
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
// If we should end then previous continuous event and start a new non-continuous event
if (event && event->Frames()
&& !event->AlarmFrames()
&& (event_close_mode == CLOSE_ALARM)
&& ((timestamp - event->StartTime()) >= min_section_length)
// FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead
&& ((snap->timestamp - event->StartTime()) >= min_section_length)
&& ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) {
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
name.c_str(), image_count, event->Id());
@ -2091,7 +2107,7 @@ bool Monitor::Analyse() {
Event::PreAlarmCount(), pre_event_count,
event->Frames(),
event->AlarmFrames(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(min_section_length).count()),
(event_close_mode == CLOSE_ALARM));
}
@ -2101,12 +2117,10 @@ bool Monitor::Analyse() {
if (!event) {
event = openEvent(snap, cause, noteSetMap);
shared_data->state = state = ALARM;
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id());
} else {
shared_data->state = state = ALARM;
} // end if no event, so start it
if ( alarm_frame_count ) {
shared_data->state = state = ALARM;
if (alarm_frame_count) {
Debug(1, "alarm frame count so SavePreAlarmFrames");
event->SavePreAlarmFrames();
}
@ -2128,13 +2142,14 @@ bool Monitor::Analyse() {
Debug(1, "Was in TAPE, going into ALARM");
} else {
Debug(1, "Staying in %s", State_Strings[state].c_str());
}
if (state == ALARM) {
last_alarm_count = analysis_image_count;
} // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT
} else { // no score?
} else if (!score and snap->score == 0) { // snap->score means -1 which means didn't do motion detection so don't do state transition
Debug(1, "!score");
alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count
if (state == ALARM) {
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
shared_data->state = state = ALERT;
@ -2142,7 +2157,7 @@ bool Monitor::Analyse() {
if (
((analysis_image_count - last_alarm_count) > post_event_count)
&&
((timestamp - event->StartTime()) >= min_section_length)) {
((snap->timestamp - event->StartTime()) >= min_section_length)) {
Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images",
name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
if (
@ -2169,7 +2184,7 @@ bool Monitor::Analyse() {
analysis_image_count,
last_alarm_count,
post_event_count,
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(GetVideoWriterStartTime().time_since_epoch()).count()),
static_cast<int64>(Seconds(min_section_length).count()));
}
@ -2177,46 +2192,22 @@ bool Monitor::Analyse() {
Event::EmptyPreAlarmFrames();
} // end if score or not
snap->score = score;
if (score > snap->score)
snap->score = score;
if (state == PREALARM) {
// Generate analysis images if necessary
if (snap->image) {
for (const Zone &zone : zones) {
if (zone.Alarmed() and zone.AlarmImage()) {
if (!snap->analysis_image)
snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zone.AlarmImage()));
} // end if zone is alarmed
} // end foreach zone
if (snap->analysis_image != nullptr)
alarm_image.Assign(*(snap->analysis_image));
} // end if image.
// incremement pre alarm image count
Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr);
Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr);
} else if (state == ALARM) {
if (snap->image) {
for (const Zone &zone : zones) {
if (zone.Alarmed() and zone.AlarmImage()) {
if (!snap->analysis_image)
snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zone.AlarmImage()));
} // end if zone is alarmed
} // end foreach zone
if (snap->analysis_image != nullptr)
alarm_image.Assign(*(snap->analysis_image));
}
if (event) {
if (noteSetMap.size() > 0)
event->updateNotes(noteSetMap);
if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) {
if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) {
Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64,
name.c_str(), analysis_image_count, event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(GetVideoWriterStartTime().time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - GetVideoWriterStartTime()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(snap->timestamp - GetVideoWriterStartTime()).count()),
static_cast<int64>(Seconds(section_length).count()));
closeEvent();
event = openEvent(snap, cause, noteSetMap);
@ -2224,15 +2215,15 @@ bool Monitor::Analyse() {
} else {
Error("ALARM but no event");
}
} else if ( state == ALERT ) {
} else if (state == ALERT) {
// Alert means this frame has no motion, but we were alarmed and are still recording.
if ((noteSetMap.size() > 0) and event)
event->updateNotes(noteSetMap);
} else if ( state == TAPE ) {
} else if (state == TAPE) {
// bulk frame code moved to event.
} // end if state machine
if ( (function == MODECT or function == MOCORD) and snap->image ) {
if ((function == MODECT or function == MOCORD) and snap->image) {
if (!ref_image.Buffer()) {
Debug(1, "Assigning");
ref_image.Assign(*(snap->image));
@ -2259,11 +2250,18 @@ bool Monitor::Analyse() {
// In the case where people have pre-alarm frames, the web ui will generate the frame images
// from the mp4. So no one will notice anyways.
if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) {
Debug(1, "Deleting image data for %d", snap->image_index);
// Don't need raw images anymore
delete snap->image;
snap->image = nullptr;
if (snap->image and (videowriter == PASSTHROUGH)) {
if (!savejpegs) {
Debug(1, "Deleting image data for %d", snap->image_index);
// Don't need raw images anymore
delete snap->image;
snap->image = nullptr;
}
if (snap->analysis_image and !(savejpegs & 2)) {
Debug(1, "Deleting analysis image data for %d", snap->image_index);
delete snap->analysis_image;
snap->analysis_image = nullptr;
}
}
packetqueue.clearPackets(snap);
@ -2274,7 +2272,8 @@ bool Monitor::Analyse() {
analysis_image_count++;
}
packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock);
delete packet_lock;
//packetqueue.unlock(packet_lock);
shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
return true;

@ -1 +1 @@
Subproject commit 14292374ccf1328f2d5db20897bd06f99ba4d938
Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef