Merge branch 'master' into multistream
This commit is contained in:
commit
3504b13e2a
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
} elsif (@needsReload) {
|
||||
foreach my $monitor (@needsReload) {
|
||||
$monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id());
|
||||
if ( $$monitor{Function} eq 'None' ) {
|
||||
delete $monitors{$monitor->Id()};
|
||||
} elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) {
|
||||
delete $monitors{$monitor->Id()};
|
||||
} else {
|
||||
if ($monitor->connect()) {
|
||||
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@needsReload = ();
|
||||
}
|
||||
|
@ -317,40 +322,21 @@ while (!$zm_terminate) {
|
|||
Info('Trigger daemon exiting');
|
||||
exit;
|
||||
|
||||
sub loadMonitor {
|
||||
my $monitor = shift;
|
||||
|
||||
Debug('Loading monitor '.$monitor);
|
||||
zmMemInvalidate($monitor);
|
||||
|
||||
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
||||
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||
}
|
||||
} # end sub loadMonitor
|
||||
|
||||
sub loadMonitors {
|
||||
$monitor_reload_time = time();
|
||||
|
||||
my %new_monitors = ();
|
||||
%monitors = ();
|
||||
|
||||
my $sql = 'SELECT * FROM `Monitors`
|
||||
WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'.
|
||||
( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
|
||||
;
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
|
||||
while ( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
||||
foreach my $monitor ( ZoneMinder::Monitor->find(
|
||||
Function=>['Modect','Mocord','Nodect','Record'],
|
||||
($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()),
|
||||
)) {
|
||||
if ($monitor->connect()) { # This will re-init shared memory
|
||||
$monitor->{LastState} = zmGetMonitorState($monitor);
|
||||
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
||||
}
|
||||
$new_monitors{$monitor->{Id}} = $monitor;
|
||||
$monitors{$monitor->{Id}} = $monitor;
|
||||
} # end while fetchrow
|
||||
%monitors = %new_monitors;
|
||||
} # end sub loadMonitors
|
||||
|
||||
sub handleMessage {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1866,12 +1866,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?");
|
||||
|
@ -1879,6 +1877,7 @@ bool Monitor::Analyse() {
|
|||
return false;
|
||||
}
|
||||
|
||||
int score = 0;
|
||||
std::string cause;
|
||||
Event::StringSetMap noteSetMap;
|
||||
|
||||
|
@ -1887,11 +1886,18 @@ bool Monitor::Analyse() {
|
|||
score += 9;
|
||||
Debug(1, "Triggered on ONVIF");
|
||||
if (!event) {
|
||||
Event::StringSet noteSet;
|
||||
noteSet.insert("ONVIF2");
|
||||
noteSetMap[MOTION_CAUSE] = noteSet;
|
||||
|
||||
event = openEvent(snap, "ONVIF", noteSetMap);
|
||||
cause += "ONVIF";
|
||||
} else {
|
||||
event->addNote(MOTION_CAUSE, "ONVIF2");
|
||||
// Add to cause
|
||||
}
|
||||
Event::StringSet noteSet;
|
||||
noteSet.insert("ONVIF2");
|
||||
noteSetMap[MOTION_CAUSE] = noteSet;
|
||||
// Regardless of previous state, we go to ALARM
|
||||
shared_data->state = state = ALARM;
|
||||
//If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm.
|
||||
if (!ONVIF_Closes_Event && state == ALARM)
|
||||
ONVIF_Trigger_State = FALSE;
|
||||
|
@ -1903,11 +1909,16 @@ bool Monitor::Analyse() {
|
|||
score += trigger_data->trigger_score;
|
||||
Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score);
|
||||
if (!event) {
|
||||
cause += trigger_data->trigger_cause;
|
||||
Event::StringSet noteSet;
|
||||
noteSet.insert(trigger_data->trigger_text);
|
||||
noteSetMap[trigger_data->trigger_cause] = noteSet;
|
||||
event = openEvent(snap, trigger_data->trigger_cause, noteSetMap);
|
||||
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id());
|
||||
} else {
|
||||
event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text);
|
||||
// Need to know if we should end the previous and start a new one, or just add the data
|
||||
}
|
||||
Event::StringSet noteSet;
|
||||
noteSet.insert(trigger_data->trigger_text);
|
||||
noteSetMap[trigger_data->trigger_cause] = noteSet;
|
||||
shared_data->state = state = ALARM;
|
||||
} // end if trigger_on
|
||||
|
||||
// FIXME this snap might not be the one that caused the signal change. Need to store that in the packet.
|
||||
|
@ -1921,7 +1932,7 @@ bool Monitor::Analyse() {
|
|||
}
|
||||
} else if (recording == RECORDING_ALWAYS) {
|
||||
if (!event) {
|
||||
if (cause.length()) cause += ", ";
|
||||
if (!cause.empty()) cause += ", ";
|
||||
cause += SIGNAL_CAUSE + std::string(": Reacquired");
|
||||
} else {
|
||||
event->addNote(SIGNAL_CAUSE, "Reacquired");
|
||||
|
@ -1979,12 +1990,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 (shared_data->analysing) {
|
||||
Debug(3, "signal and capturing and doing motion detection");
|
||||
|
||||
|
@ -1996,18 +2005,20 @@ 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))) {
|
||||
Event::StringSet zoneSet;
|
||||
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) {
|
||||
|
@ -2017,25 +2028,29 @@ 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 analysing %d", shared_data->analysing);
|
||||
} // end if active and doing motion detection
|
||||
|
@ -2051,9 +2066,9 @@ bool Monitor::Analyse() {
|
|||
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
|
||||
|
@ -2071,7 +2086,7 @@ bool Monitor::Analyse() {
|
|||
} // end if ! event
|
||||
} // end if RECORDING
|
||||
|
||||
if (score) {
|
||||
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) {
|
||||
|
@ -2090,7 +2105,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));
|
||||
}
|
||||
|
@ -2105,11 +2120,12 @@ bool Monitor::Analyse() {
|
|||
if (!event and (recording != RECORDING_NONE)) {
|
||||
event = openEvent(snap, cause, noteSetMap);
|
||||
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id());
|
||||
if (alarm_frame_count) {
|
||||
Debug(1, "alarm frame count so SavePreAlarmFrames");
|
||||
event->SavePreAlarmFrames();
|
||||
}
|
||||
} // 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();
|
||||
}
|
||||
} else if (state != PREALARM) {
|
||||
Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count);
|
||||
shared_data->state = state = PREALARM;
|
||||
|
@ -2133,15 +2149,17 @@ bool Monitor::Analyse() {
|
|||
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;
|
||||
} else if (state == ALERT) {
|
||||
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 ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE )
|
||||
|
@ -2172,7 +2190,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()));
|
||||
}
|
||||
|
@ -2180,46 +2198,22 @@ bool Monitor::Analyse() {
|
|||
Event::EmptyPreAlarmFrames();
|
||||
} // end if score or not
|
||||
|
||||
snap->score = score;
|
||||
if (score > snap->score)
|
||||
snap->score = score;
|
||||
|
||||
if (state == PREALARM) {
|
||||
// Generate analysis images if necessary
|
||||
if (snap->image) {
|
||||
for (const Zone &zone : zones) {
|
||||
if (zone.Alarmed() and zone.AlarmImage()) {
|
||||
if (!snap->analysis_image)
|
||||
snap->analysis_image = new Image(*(snap->image));
|
||||
snap->analysis_image->Overlay(*(zone.AlarmImage()));
|
||||
} // end if zone is alarmed
|
||||
} // end foreach zone
|
||||
if (snap->analysis_image != nullptr)
|
||||
alarm_image.Assign(*(snap->analysis_image));
|
||||
} // end if image.
|
||||
|
||||
// incremement pre alarm image count
|
||||
Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr);
|
||||
Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr);
|
||||
} else if (state == ALARM) {
|
||||
if (snap->image) {
|
||||
for (const Zone &zone : zones) {
|
||||
if (zone.Alarmed() and zone.AlarmImage()) {
|
||||
if (!snap->analysis_image)
|
||||
snap->analysis_image = new Image(*(snap->image));
|
||||
snap->analysis_image->Overlay(*(zone.AlarmImage()));
|
||||
} // end if zone is alarmed
|
||||
} // end foreach zone
|
||||
if (snap->analysis_image != nullptr)
|
||||
alarm_image.Assign(*(snap->analysis_image));
|
||||
}
|
||||
|
||||
if (event) {
|
||||
if (noteSetMap.size() > 0)
|
||||
event->updateNotes(noteSetMap);
|
||||
if (section_length != Seconds(min_section_length) && (timestamp - event->StartTime() > section_length)) {
|
||||
if (section_length != Seconds(min_section_length) && (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);
|
||||
|
@ -2227,11 +2221,11 @@ bool Monitor::Analyse() {
|
|||
} else {
|
||||
Error("ALARM but no event");
|
||||
}
|
||||
} else if ( state == ALERT ) {
|
||||
} else if (state == ALERT) {
|
||||
// Alert means this frame has no motion, but we were alarmed and are still recording.
|
||||
if ((noteSetMap.size() > 0) and event)
|
||||
event->updateNotes(noteSetMap);
|
||||
} else if ( state == TAPE ) {
|
||||
} else if (state == TAPE) {
|
||||
// bulk frame code moved to event.
|
||||
} // end if state machine
|
||||
|
||||
|
@ -2262,11 +2256,18 @@ bool Monitor::Analyse() {
|
|||
|
||||
// In the case where people have pre-alarm frames, the web ui will generate the frame images
|
||||
// from the mp4. So no one will notice anyways.
|
||||
if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) {
|
||||
Debug(1, "Deleting image data for %d", snap->image_index);
|
||||
// Don't need raw images anymore
|
||||
delete snap->image;
|
||||
snap->image = nullptr;
|
||||
if (snap->image and (videowriter == PASSTHROUGH)) {
|
||||
if (!savejpegs) {
|
||||
Debug(1, "Deleting image data for %d", snap->image_index);
|
||||
// Don't need raw images anymore
|
||||
delete snap->image;
|
||||
snap->image = nullptr;
|
||||
}
|
||||
if (snap->analysis_image and !(savejpegs & 2)) {
|
||||
Debug(1, "Deleting analysis image data for %d", snap->image_index);
|
||||
delete snap->analysis_image;
|
||||
snap->analysis_image = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
packetqueue.clearPackets(snap);
|
||||
|
@ -2277,7 +2278,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;
|
||||
|
|
Loading…
Reference in New Issue