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
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
$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 {
|
||||
|
|
|
@ -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,13 +31,15 @@ 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
|
||||
-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>
|
||||
|
@ -50,10 +52,9 @@ configuring upgrades etc, including on the fly upgrades.
|
|||
|
||||
=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,6 +1041,8 @@ sub patchDB {
|
|||
} # end sub patchDB
|
||||
|
||||
sub migratePasswords {
|
||||
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() );
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
216
src/zm_event.cpp
216
src/zm_event.cpp
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ¬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:
|
||||
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);
|
||||
|
|
|
@ -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) {
|
||||
cause += "ONVIF";
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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,11 +2117,9 @@ 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
|
||||
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
|
||||
|
||||
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);
|
||||
|
@ -2259,12 +2250,19 @@ 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) {
|
||||
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
|
Loading…
Reference in New Issue