Merge branch 'ZoneMinder:master' into janus
This commit is contained in:
commit
212d51f933
|
@ -1 +1 @@
|
||||||
Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f
|
Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878
|
|
@ -5,7 +5,8 @@
|
||||||
Description=ZoneMinder CCTV recording and surveillance system
|
Description=ZoneMinder CCTV recording and surveillance system
|
||||||
After=network.target mysql.service
|
After=network.target mysql.service
|
||||||
# Remarked out so that it will start ZM on machines that don't have mysql installed
|
# 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]
|
[Service]
|
||||||
#User=www-data
|
#User=www-data
|
||||||
|
|
|
@ -273,6 +273,9 @@ sub zmDbDo {
|
||||||
if ( ! defined $rows ) {
|
if ( ! defined $rows ) {
|
||||||
$sql =~ s/\?/'%s'/;
|
$sql =~ s/\?/'%s'/;
|
||||||
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
|
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
|
||||||
|
} elsif ( ZoneMinder::Logger::logLevel() > INFO ) {
|
||||||
|
$sql =~ s/\?/'%s'/;
|
||||||
|
Debug(sprintf("Succeeded $sql : $rows rows affected", @_));
|
||||||
}
|
}
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
|
@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL;
|
||||||
|
|
||||||
if ( ! ( $db_field =~ /\?/ ) ) {
|
if ( ! ( $db_field =~ /\?/ ) ) {
|
||||||
if ( @{$$search{$k}} != 1 ) {
|
if ( @{$$search{$k}} != 1 ) {
|
||||||
push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
|
push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
|
||||||
} else {
|
} else {
|
||||||
push @where, $db_field.'=?';
|
push @where, '`'.$db_field.'`=?';
|
||||||
} # end if
|
} # end if
|
||||||
} else {
|
} else {
|
||||||
$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
|
$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}} ) {
|
foreach my $p_k ( keys %{$$search{$k}} ) {
|
||||||
my $v = $$search{$k}{$p_k};
|
my $v = $$search{$k}{$p_k};
|
||||||
if ( ref $v eq 'ARRAY' ) {
|
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};
|
push @values, $p_k, @{$v};
|
||||||
} else {
|
} else {
|
||||||
push @where, $db_field.'=?';
|
push @where, '`'.$db_field.'`=?';
|
||||||
push @values, $p_k, $v;
|
push @values, $p_k, $v;
|
||||||
} # end if
|
} # end if
|
||||||
} # end foreach p_k
|
} # 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';
|
push @where, $db_field.' IS NULL';
|
||||||
} else {
|
} else {
|
||||||
if ( ! ( $db_field =~ /\?/ ) ) {
|
if ( ! ( $db_field =~ /\?/ ) ) {
|
||||||
push @where, $db_field .'=?';
|
push @where, '`'.$db_field .'`=?';
|
||||||
} else {
|
} else {
|
||||||
push @where, $db_field;
|
push @where, $db_field;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25;
|
||||||
|
|
||||||
@EXTRA_PERL_LIB@
|
@EXTRA_PERL_LIB@
|
||||||
use ZoneMinder;
|
use ZoneMinder;
|
||||||
|
use ZoneMinder::Monitor;
|
||||||
use ZoneMinder::Trigger::Channel::Inet;
|
use ZoneMinder::Trigger::Channel::Inet;
|
||||||
use ZoneMinder::Trigger::Channel::Unix;
|
use ZoneMinder::Trigger::Channel::Unix;
|
||||||
use ZoneMinder::Trigger::Channel::Serial;
|
use ZoneMinder::Trigger::Channel::Serial;
|
||||||
|
@ -203,14 +204,10 @@ while (!$zm_terminate) {
|
||||||
# Check for alarms that might have happened
|
# Check for alarms that might have happened
|
||||||
my @out_messages;
|
my @out_messages;
|
||||||
foreach my $monitor ( values %monitors ) {
|
foreach my $monitor ( values %monitors ) {
|
||||||
if ($$monitor{Function} eq 'None') {
|
if (!$monitor->connect()) {
|
||||||
$monitor_reload_time = 0;
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!zmMemVerify($monitor)) {
|
|
||||||
# Our attempt to verify the memory handle failed. We should reload the monitors.
|
# 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.
|
# Don't need to zmMemInvalidate because the monitor reload will do it.
|
||||||
|
Debug("Failed connect, putting on reloads");
|
||||||
push @needsReload, $monitor;
|
push @needsReload, $monitor;
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
@ -249,6 +246,7 @@ while (!$zm_terminate) {
|
||||||
}
|
}
|
||||||
$monitor->{LastState} = $state;
|
$monitor->{LastState} = $state;
|
||||||
$monitor->{LastEvent} = $last_event;
|
$monitor->{LastEvent} = $last_event;
|
||||||
|
$monitor->disconnect();
|
||||||
} # end foreach monitor
|
} # end foreach monitor
|
||||||
|
|
||||||
foreach my $connection ( @out_connections ) {
|
foreach my $connection ( @out_connections ) {
|
||||||
|
@ -298,15 +296,22 @@ while (!$zm_terminate) {
|
||||||
|
|
||||||
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
|
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
|
||||||
if ( (time() - $monitor_reload_time) > 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();
|
loadMonitors();
|
||||||
@needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
|
@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
|
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
|
||||||
} elsif ( @needsReload ) {
|
} elsif (@needsReload) {
|
||||||
foreach my $monitor ( @needsReload ) {
|
foreach my $monitor (@needsReload) {
|
||||||
loadMonitor($monitor);
|
$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 = ();
|
@needsReload = ();
|
||||||
}
|
}
|
||||||
|
@ -317,40 +322,21 @@ while (!$zm_terminate) {
|
||||||
Info('Trigger daemon exiting');
|
Info('Trigger daemon exiting');
|
||||||
exit;
|
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 {
|
sub loadMonitors {
|
||||||
$monitor_reload_time = time();
|
$monitor_reload_time = time();
|
||||||
|
|
||||||
my %new_monitors = ();
|
%monitors = ();
|
||||||
|
|
||||||
my $sql = 'SELECT * FROM `Monitors`
|
foreach my $monitor ( ZoneMinder::Monitor->find(
|
||||||
WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'.
|
Function=>['Modect','Mocord','Nodect','Record'],
|
||||||
( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
|
($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()),
|
||||||
;
|
)) {
|
||||||
my $sth = $dbh->prepare_cached( $sql )
|
if ($monitor->connect()) { # This will re-init shared memory
|
||||||
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
|
|
||||||
$monitor->{LastState} = zmGetMonitorState($monitor);
|
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||||
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||||
}
|
}
|
||||||
$new_monitors{$monitor->{Id}} = $monitor;
|
$monitors{$monitor->{Id}} = $monitor;
|
||||||
} # end while fetchrow
|
} # end while fetchrow
|
||||||
%monitors = %new_monitors;
|
|
||||||
} # end sub loadMonitors
|
} # end sub loadMonitors
|
||||||
|
|
||||||
sub handleMessage {
|
sub handleMessage {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
# ZoneMinder Update Script, $Date$, $Revision$
|
# ZoneMinder Update Script
|
||||||
# Copyright (C) 2001-2008 Philip Coombes
|
# Copyright (C) 2001-2008 Philip Coombes
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# 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
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
This script just checks what the most recent release of ZoneMinder is
|
This script checks what the most recent release of ZoneMinder is
|
||||||
at the the moment. It will eventually be responsible for applying and
|
at the the moment by downloading https://update.zoneminder.com/version.txt.
|
||||||
configuring upgrades etc, including on the fly upgrades.
|
It can also apply and configure upgrades etc, including on the fly upgrades.
|
||||||
|
|
||||||
=head1 OPTIONS
|
=head1 OPTIONS
|
||||||
|
|
||||||
-c, --check - Check for updated versions of ZoneMinder
|
-c, --check - Check for updated versions of ZoneMinder.
|
||||||
-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi
|
If not interactive zmupdate.pl will stay running, checking every hour.
|
||||||
--migrate-events - Update database structures as per USE_DEEP_STORAGE setting.
|
If interactive will try once, print out result and quit.
|
||||||
-v <version>, --version=<version> - Force upgrade to the current version from <version>
|
-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi
|
||||||
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
|
--migrate-events - Update database structures as per USE_DEEP_STORAGE setting.
|
||||||
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
|
-v <version>, --version=<version> - Force upgrade to the current version from <version>
|
||||||
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
|
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
|
||||||
-d <dir>, --dir=<dir> - Directory containing update files if not in default build location
|
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
|
||||||
-interactive - interact with the user
|
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
|
||||||
-nointeractive - do not interact with the user
|
-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
|
=cut
|
||||||
use strict;
|
use strict;
|
||||||
|
use warnings;
|
||||||
use bytes;
|
use bytes;
|
||||||
use version;
|
use version;
|
||||||
use Crypt::Eksblowfish::Bcrypt;
|
|
||||||
use Data::Entropy::Algorithms qw(rand_bits);
|
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
|
@ -122,9 +123,8 @@ GetOptions(
|
||||||
) or pod2usage(-exitstatus => -1);
|
) or pod2usage(-exitstatus => -1);
|
||||||
|
|
||||||
my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } );
|
my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } );
|
||||||
if ( !$dbh ) {
|
die "Unable to connect to db\n" if !$dbh;
|
||||||
die "Unable to connect to db\n";
|
|
||||||
}
|
|
||||||
$Config{ZM_DB_USER} = $dbUser;
|
$Config{ZM_DB_USER} = $dbUser;
|
||||||
$Config{ZM_DB_PASS} = $dbPass;
|
$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.
|
# 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);
|
pod2usage(-exitstatus => -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
|
if ($check and ($Config{ZM_CHECK_FOR_UPDATES} or $interactive) ) {
|
||||||
print('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n");
|
if (!$interactive) {
|
||||||
|
Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n");
|
||||||
|
}
|
||||||
|
|
||||||
my $currVersion = $Config{ZM_DYN_CURR_VERSION};
|
my $currVersion = $Config{ZM_DYN_CURR_VERSION};
|
||||||
my $lastVersion = $Config{ZM_DYN_LAST_VERSION};
|
my $lastVersion = $Config{ZM_DYN_LAST_VERSION};
|
||||||
|
@ -153,16 +155,14 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
|
||||||
|
|
||||||
if ( !$currVersion ) {
|
if ( !$currVersion ) {
|
||||||
$currVersion = $Config{ZM_VERSION};
|
$currVersion = $Config{ZM_VERSION};
|
||||||
|
zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while ( 1 ) {
|
while ( 1 ) {
|
||||||
my $now = time();
|
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');
|
Info('Checking for updates');
|
||||||
|
|
||||||
use LWP::UserAgent;
|
use LWP::UserAgent;
|
||||||
|
@ -175,21 +175,18 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
|
||||||
my $res = $ua->request($req);
|
my $res = $ua->request($req);
|
||||||
|
|
||||||
if ( $res->is_success ) {
|
if ( $res->is_success ) {
|
||||||
$lastVersion = $res->content;
|
my $latestVersion = $res->content;
|
||||||
chomp($lastVersion);
|
chomp($latestVersion);
|
||||||
$lastCheck = $now;
|
$lastCheck = $now;
|
||||||
|
|
||||||
Info('Got version: '.$lastVersion);
|
Info('Got version: '.$latestVersion);
|
||||||
|
|
||||||
my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'';
|
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion);
|
||||||
my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr());
|
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck);
|
||||||
my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr());
|
if ($interactive) {
|
||||||
$lv_sth->finish();
|
print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n");
|
||||||
|
exit(0);
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
Error('Error check failed: \''.$res->status_line().'\'');
|
Error('Error check failed: \''.$res->status_line().'\'');
|
||||||
}
|
}
|
||||||
|
@ -1044,14 +1041,16 @@ sub patchDB {
|
||||||
} # end sub patchDB
|
} # end sub patchDB
|
||||||
|
|
||||||
sub migratePasswords {
|
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 $sql = 'SELECT * FROM `Users`';
|
||||||
my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
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());
|
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);
|
my $scheme = substr($user->{Password}, 0, 1);
|
||||||
if ($scheme eq '*') {
|
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 $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
|
||||||
my $settings = '$2a$10$'.$salt;
|
my $settings = '$2a$10$'.$salt;
|
||||||
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
|
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
|
||||||
|
|
|
@ -23,6 +23,14 @@ void AnalysisThread::Start() {
|
||||||
|
|
||||||
void AnalysisThread::Run() {
|
void AnalysisThread::Run() {
|
||||||
while (!(terminate_ or zm_terminate)) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
216
src/zm_event.cpp
216
src/zm_event.cpp
|
@ -65,7 +65,8 @@ Event::Event(
|
||||||
last_db_frame(0),
|
last_db_frame(0),
|
||||||
have_video_keyframe(false),
|
have_video_keyframe(false),
|
||||||
//scheme
|
//scheme
|
||||||
save_jpegs(0)
|
save_jpegs(0),
|
||||||
|
terminate_(false)
|
||||||
{
|
{
|
||||||
std::string notes;
|
std::string notes;
|
||||||
createNotes(notes);
|
createNotes(notes);
|
||||||
|
@ -133,98 +134,22 @@ Event::Event(
|
||||||
);
|
);
|
||||||
id = zmDbDoInsert(sql);
|
id = zmDbDoInsert(sql);
|
||||||
|
|
||||||
if (!SetPath(storage)) {
|
thread_ = std::thread(&Event::Run, this);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::~Event() {
|
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 */
|
/* 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) {
|
if (videoStore != nullptr) {
|
||||||
Debug(4, "Deleting video store");
|
Debug(4, "Deleting video store");
|
||||||
delete videoStore;
|
delete videoStore;
|
||||||
|
@ -377,6 +302,12 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
|
||||||
} // void Event::updateNotes(const StringSetMap &newNoteSetMap)
|
} // void Event::updateNotes(const StringSetMap &newNoteSetMap)
|
||||||
|
|
||||||
void Event::AddPacket(const std::shared_ptr<ZMPacket>&packet) {
|
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 ||
|
have_video_keyframe = have_video_keyframe ||
|
||||||
( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) &&
|
( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) &&
|
||||||
( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) );
|
( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) );
|
||||||
|
@ -655,3 +586,112 @@ bool Event::SetPath(Storage *storage) {
|
||||||
} // deep storage or not
|
} // deep storage or not
|
||||||
return true;
|
return true;
|
||||||
} // end bool Event::SetPath
|
} // 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,14 +22,21 @@
|
||||||
|
|
||||||
#include "zm_config.h"
|
#include "zm_config.h"
|
||||||
#include "zm_define.h"
|
#include "zm_define.h"
|
||||||
|
#include "zm_packet.h"
|
||||||
#include "zm_storage.h"
|
#include "zm_storage.h"
|
||||||
#include "zm_time.h"
|
#include "zm_time.h"
|
||||||
#include "zm_utils.h"
|
#include "zm_utils.h"
|
||||||
#include "zm_zone.h"
|
#include "zm_zone.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
class EventStream;
|
class EventStream;
|
||||||
class Frame;
|
class Frame;
|
||||||
|
@ -98,6 +105,15 @@ class Event {
|
||||||
|
|
||||||
void createNotes(std::string ¬es);
|
void createNotes(std::string ¬es);
|
||||||
|
|
||||||
|
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:
|
public:
|
||||||
static bool OpenFrameSocket(int);
|
static bool OpenFrameSocket(int);
|
||||||
static bool ValidateFrameSocket(int);
|
static bool ValidateFrameSocket(int);
|
||||||
|
@ -118,6 +134,7 @@ class Event {
|
||||||
SystemTimePoint EndTime() const { return end_time; }
|
SystemTimePoint EndTime() const { return end_time; }
|
||||||
|
|
||||||
void AddPacket(const std::shared_ptr<ZMPacket> &p);
|
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 WritePacket(const std::shared_ptr<ZMPacket> &p);
|
||||||
bool SendFrameImage(const Image *image, bool alarm_frame=false);
|
bool SendFrameImage(const Image *image, bool alarm_frame=false);
|
||||||
bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const;
|
bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const;
|
||||||
|
@ -130,6 +147,12 @@ class Event {
|
||||||
int score = 0,
|
int score = 0,
|
||||||
Image *alarm_image = nullptr);
|
Image *alarm_image = nullptr);
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
terminate_ = true;
|
||||||
|
packet_queue_condition.notify_all();
|
||||||
|
}
|
||||||
|
bool Stopped() const { return terminate_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void WriteDbFrames();
|
void WriteDbFrames();
|
||||||
bool SetPath(Storage *storage);
|
bool SetPath(Storage *storage);
|
||||||
|
|
|
@ -1862,12 +1862,10 @@ bool Monitor::Analyse() {
|
||||||
|
|
||||||
// Need to guard around event creation/deletion from Reload()
|
// Need to guard around event creation/deletion from Reload()
|
||||||
std::lock_guard<std::mutex> lck(event_mutex);
|
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 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) {
|
if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) {
|
||||||
Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state));
|
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
|
// Ready means that we have captured the warmup # of frames
|
||||||
if (!Ready()) {
|
if (!Ready()) {
|
||||||
Debug(3, "Not ready?");
|
Debug(3, "Not ready?");
|
||||||
|
@ -1875,6 +1873,7 @@ bool Monitor::Analyse() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
std::string cause;
|
std::string cause;
|
||||||
Event::StringSetMap noteSetMap;
|
Event::StringSetMap noteSetMap;
|
||||||
|
|
||||||
|
@ -1883,11 +1882,18 @@ bool Monitor::Analyse() {
|
||||||
score += 9;
|
score += 9;
|
||||||
Debug(1, "Triggered on ONVIF");
|
Debug(1, "Triggered on ONVIF");
|
||||||
if (!event) {
|
if (!event) {
|
||||||
cause += "ONVIF";
|
|
||||||
}
|
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
noteSet.insert("ONVIF2");
|
noteSet.insert("ONVIF2");
|
||||||
noteSetMap[MOTION_CAUSE] = noteSet;
|
noteSetMap[MOTION_CAUSE] = noteSet;
|
||||||
|
|
||||||
|
event = openEvent(snap, "ONVIF", noteSetMap);
|
||||||
|
cause += "ONVIF";
|
||||||
|
} else {
|
||||||
|
event->addNote(MOTION_CAUSE, "ONVIF2");
|
||||||
|
// Add to cause
|
||||||
|
}
|
||||||
|
// 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 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)
|
if (!ONVIF_Closes_Event && state == ALARM)
|
||||||
ONVIF_Trigger_State = FALSE;
|
ONVIF_Trigger_State = FALSE;
|
||||||
|
@ -1899,11 +1905,16 @@ bool Monitor::Analyse() {
|
||||||
score += trigger_data->trigger_score;
|
score += trigger_data->trigger_score;
|
||||||
Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score);
|
Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
cause += trigger_data->trigger_cause;
|
|
||||||
}
|
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
noteSet.insert(trigger_data->trigger_text);
|
noteSet.insert(trigger_data->trigger_text);
|
||||||
noteSetMap[trigger_data->trigger_cause] = noteSet;
|
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
|
||||||
|
}
|
||||||
|
shared_data->state = state = ALARM;
|
||||||
} // end if trigger_on
|
} // end if trigger_on
|
||||||
|
|
||||||
// FIXME this snap might not be the one that caused the signal change. Need to store that in the packet.
|
// 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) {
|
} else if (function == MOCORD or function == RECORD) {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
if (cause.length()) cause += ", ";
|
if (!cause.empty()) cause += ", ";
|
||||||
cause += SIGNAL_CAUSE + std::string(": Reacquired");
|
cause += SIGNAL_CAUSE + std::string(": Reacquired");
|
||||||
} else {
|
} else {
|
||||||
event->addNote(SIGNAL_CAUSE, "Reacquired");
|
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.unlock(packet_lock); // This will delete packet_lock and notify_all
|
||||||
packetqueue.wait();
|
packetqueue.wait();
|
||||||
// Everything may have changed, just return and start again. This needs to be more RAII
|
// Everything may have changed, just return and start again. This needs to be more RAII
|
||||||
return false;
|
return true;
|
||||||
} // end while ! decoded
|
} // end while ! decoded
|
||||||
} // end if decoding enabled
|
} // end if decoding enabled
|
||||||
|
|
||||||
SystemTimePoint timestamp = snap->timestamp;
|
|
||||||
|
|
||||||
if (Active() and (function == MODECT or function == MOCORD)) {
|
if (Active() and (function == MODECT or function == MOCORD)) {
|
||||||
Debug(3, "signal and active and modect");
|
Debug(3, "signal and active and modect");
|
||||||
Event::StringSet zoneSet;
|
Event::StringSet zoneSet;
|
||||||
|
@ -1994,17 +2003,19 @@ bool Monitor::Analyse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snap->image) {
|
if (snap->image) {
|
||||||
alarm_image.Assign(*(snap->image));
|
|
||||||
|
|
||||||
// decoder may not have been able to provide an image
|
// decoder may not have been able to provide an image
|
||||||
if (!ref_image.Buffer()) {
|
if (!ref_image.Buffer()) {
|
||||||
Debug(1, "Assigning instead of Detecting");
|
Debug(1, "Assigning instead of Detecting");
|
||||||
ref_image.Assign(*(snap->image));
|
ref_image.Assign(*(snap->image));
|
||||||
|
alarm_image.Assign(*(snap->image));
|
||||||
} else if (!(analysis_image_count % (motion_frame_skip+1))) {
|
} else if (!(analysis_image_count % (motion_frame_skip+1))) {
|
||||||
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
|
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
|
||||||
// Get new score.
|
// 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
|
// lets construct alarm cause. It will contain cause + names of zones alarmed
|
||||||
snap->zone_stats.reserve(zones.size());
|
snap->zone_stats.reserve(zones.size());
|
||||||
for (const Zone &zone : zones) {
|
for (const Zone &zone : zones) {
|
||||||
|
@ -2014,27 +2025,31 @@ bool Monitor::Analyse() {
|
||||||
if (zone.Alarmed()) {
|
if (zone.Alarmed()) {
|
||||||
if (!snap->alarm_cause.empty()) snap->alarm_cause += ",";
|
if (!snap->alarm_cause.empty()) snap->alarm_cause += ",";
|
||||||
snap->alarm_cause += std::string(zone.Label());
|
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)",
|
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;
|
motion_frame_count += 1;
|
||||||
last_motion_score = motion_score;
|
last_motion_score = snap->score;
|
||||||
|
|
||||||
if (motion_score) {
|
if (snap->score) {
|
||||||
if (cause.length()) cause += ", ";
|
if (cause.length()) cause += ", ";
|
||||||
cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause;
|
cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause;
|
||||||
noteSetMap[MOTION_CAUSE] = zoneSet;
|
noteSetMap[MOTION_CAUSE] = zoneSet;
|
||||||
} // end if motion_score
|
} // end if motion_score
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Skipped motion detection last motion score was %d", last_motion_score);
|
Debug(1, "Skipped motion detection last motion score was %d", last_motion_score);
|
||||||
|
alarm_image.Assign(*(snap->image));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "no image so skipping motion detection");
|
Debug(1, "no image so skipping motion detection");
|
||||||
} // end if has image
|
} // end if has image
|
||||||
score += last_motion_score;
|
//score += last_motion_score;
|
||||||
} else {
|
} 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,
|
Active(), enabled, shared_data->active,
|
||||||
(function == MODECT or function == MOCORD)
|
(function == MODECT or function == MOCORD)
|
||||||
);
|
);
|
||||||
|
@ -2045,17 +2060,17 @@ bool Monitor::Analyse() {
|
||||||
if (event) {
|
if (event) {
|
||||||
Debug(2, "Have event %" PRIu64 " in record", event->Id());
|
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 == MOCORD && event_close_mode != CLOSE_TIME)
|
||||||
|| (function == RECORD && 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 ,
|
Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 ,
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
image_count,
|
image_count,
|
||||||
event->Id(),
|
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>(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()));
|
static_cast<int64>(Seconds(section_length).count()));
|
||||||
closeEvent();
|
closeEvent();
|
||||||
} // end if section_length
|
} // end if section_length
|
||||||
|
@ -2073,13 +2088,14 @@ bool Monitor::Analyse() {
|
||||||
} // end if ! event
|
} // end if ! event
|
||||||
} // end if RECORDING
|
} // end if RECORDING
|
||||||
|
|
||||||
if (score and (function != MONITOR)) {
|
if ((snap->score > 0) and (function != MONITOR)) {
|
||||||
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
|
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
|
||||||
// If we should end then previous continuous event and start a new non-continuous event
|
// If we should end then previous continuous event and start a new non-continuous event
|
||||||
if (event && event->Frames()
|
if (event && event->Frames()
|
||||||
&& !event->AlarmFrames()
|
&& !event->AlarmFrames()
|
||||||
&& (event_close_mode == CLOSE_ALARM)
|
&& (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))) {
|
&& ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) {
|
||||||
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
|
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
|
||||||
name.c_str(), image_count, event->Id());
|
name.c_str(), image_count, event->Id());
|
||||||
|
@ -2091,7 +2107,7 @@ bool Monitor::Analyse() {
|
||||||
Event::PreAlarmCount(), pre_event_count,
|
Event::PreAlarmCount(), pre_event_count,
|
||||||
event->Frames(),
|
event->Frames(),
|
||||||
event->AlarmFrames(),
|
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()),
|
static_cast<int64>(Seconds(min_section_length).count()),
|
||||||
(event_close_mode == CLOSE_ALARM));
|
(event_close_mode == CLOSE_ALARM));
|
||||||
}
|
}
|
||||||
|
@ -2101,12 +2117,10 @@ bool Monitor::Analyse() {
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
event = openEvent(snap, cause, noteSetMap);
|
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());
|
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
|
} // 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");
|
Debug(1, "alarm frame count so SavePreAlarmFrames");
|
||||||
event->SavePreAlarmFrames();
|
event->SavePreAlarmFrames();
|
||||||
}
|
}
|
||||||
|
@ -2128,13 +2142,14 @@ bool Monitor::Analyse() {
|
||||||
Debug(1, "Was in TAPE, going into ALARM");
|
Debug(1, "Was in TAPE, going into ALARM");
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Staying in %s", State_Strings[state].c_str());
|
Debug(1, "Staying in %s", State_Strings[state].c_str());
|
||||||
|
|
||||||
}
|
}
|
||||||
if (state == ALARM) {
|
if (state == ALARM) {
|
||||||
last_alarm_count = analysis_image_count;
|
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
|
} // 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
|
alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count
|
||||||
|
|
||||||
if (state == ALARM) {
|
if (state == ALARM) {
|
||||||
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
|
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
|
||||||
shared_data->state = state = ALERT;
|
shared_data->state = state = ALERT;
|
||||||
|
@ -2142,7 +2157,7 @@ bool Monitor::Analyse() {
|
||||||
if (
|
if (
|
||||||
((analysis_image_count - last_alarm_count) > post_event_count)
|
((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",
|
Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images",
|
||||||
name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
|
name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
|
||||||
if (
|
if (
|
||||||
|
@ -2169,7 +2184,7 @@ bool Monitor::Analyse() {
|
||||||
analysis_image_count,
|
analysis_image_count,
|
||||||
last_alarm_count,
|
last_alarm_count,
|
||||||
post_event_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>(std::chrono::duration_cast<Seconds>(GetVideoWriterStartTime().time_since_epoch()).count()),
|
||||||
static_cast<int64>(Seconds(min_section_length).count()));
|
static_cast<int64>(Seconds(min_section_length).count()));
|
||||||
}
|
}
|
||||||
|
@ -2177,46 +2192,22 @@ bool Monitor::Analyse() {
|
||||||
Event::EmptyPreAlarmFrames();
|
Event::EmptyPreAlarmFrames();
|
||||||
} // end if score or not
|
} // end if score or not
|
||||||
|
|
||||||
|
if (score > snap->score)
|
||||||
snap->score = score;
|
snap->score = score;
|
||||||
|
|
||||||
if (state == PREALARM) {
|
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
|
// incremement pre alarm image count
|
||||||
Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr);
|
Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr);
|
||||||
} else if (state == ALARM) {
|
} 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 (event) {
|
||||||
if (noteSetMap.size() > 0)
|
if (noteSetMap.size() > 0)
|
||||||
event->updateNotes(noteSetMap);
|
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,
|
Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64,
|
||||||
name.c_str(), analysis_image_count, event->Id(),
|
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>(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()));
|
static_cast<int64>(Seconds(section_length).count()));
|
||||||
closeEvent();
|
closeEvent();
|
||||||
event = openEvent(snap, cause, noteSetMap);
|
event = openEvent(snap, cause, noteSetMap);
|
||||||
|
@ -2224,15 +2215,15 @@ bool Monitor::Analyse() {
|
||||||
} else {
|
} else {
|
||||||
Error("ALARM but no event");
|
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.
|
// Alert means this frame has no motion, but we were alarmed and are still recording.
|
||||||
if ((noteSetMap.size() > 0) and event)
|
if ((noteSetMap.size() > 0) and event)
|
||||||
event->updateNotes(noteSetMap);
|
event->updateNotes(noteSetMap);
|
||||||
} else if ( state == TAPE ) {
|
} else if (state == TAPE) {
|
||||||
// bulk frame code moved to event.
|
// bulk frame code moved to event.
|
||||||
} // end if state machine
|
} // 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()) {
|
if (!ref_image.Buffer()) {
|
||||||
Debug(1, "Assigning");
|
Debug(1, "Assigning");
|
||||||
ref_image.Assign(*(snap->image));
|
ref_image.Assign(*(snap->image));
|
||||||
|
@ -2259,12 +2250,19 @@ bool Monitor::Analyse() {
|
||||||
|
|
||||||
// In the case where people have pre-alarm frames, the web ui will generate the frame images
|
// 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.
|
// from the mp4. So no one will notice anyways.
|
||||||
if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) {
|
if (snap->image and (videowriter == PASSTHROUGH)) {
|
||||||
|
if (!savejpegs) {
|
||||||
Debug(1, "Deleting image data for %d", snap->image_index);
|
Debug(1, "Deleting image data for %d", snap->image_index);
|
||||||
// Don't need raw images anymore
|
// Don't need raw images anymore
|
||||||
delete snap->image;
|
delete snap->image;
|
||||||
snap->image = nullptr;
|
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);
|
packetqueue.clearPackets(snap);
|
||||||
|
|
||||||
|
@ -2274,7 +2272,8 @@ bool Monitor::Analyse() {
|
||||||
analysis_image_count++;
|
analysis_image_count++;
|
||||||
}
|
}
|
||||||
packetqueue.increment_it(analysis_it);
|
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());
|
shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 14292374ccf1328f2d5db20897bd06f99ba4d938
|
Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef
|
Loading…
Reference in New Issue