zoneminder/src/zm_monitor.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

3103 lines
111 KiB
C++
Raw Normal View History

//
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_monitor.h"
#include "zm_group.h"
#include "zm_eventstream.h"
#include "zm_fifo.h"
#include "zm_file_camera.h"
#include "zm_remote_camera.h"
#include "zm_remote_camera_http.h"
2017-10-04 04:28:56 +08:00
#include "zm_remote_camera_nvsocket.h"
#include "zm_signal.h"
#include "zm_time.h"
#include "zm_utils.h"
#include "zm_zone.h"
#if ZM_HAS_V4L
#include "zm_local_camera.h"
#endif // ZM_HAS_V4L
#if HAVE_LIBAVFORMAT
#include "zm_remote_camera_rtsp.h"
#endif // HAVE_LIBAVFORMAT
#if HAVE_LIBAVFORMAT
#include "zm_ffmpeg_camera.h"
#endif // HAVE_LIBAVFORMAT
2013-12-13 01:45:29 +08:00
#if HAVE_LIBVLC
#include "zm_libvlc_camera.h"
#endif // HAVE_LIBVLC
#if HAVE_LIBCURL
#include "zm_curl_camera.h"
#endif // HAVE_LIBCURL
#if HAVE_LIBVNC
2020-03-26 14:21:34 +08:00
#include "zm_libvnc_camera.h"
#endif // HAVE_LIBVNC
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#if ZM_MEM_MAPPED
#include <sys/mman.h>
#include <fcntl.h>
#else // ZM_MEM_MAPPED
#include <sys/ipc.h>
#include <sys/shm.h>
#endif // ZM_MEM_MAPPED
// SOLARIS - we don't have MAP_LOCKED on openSolaris/illumos
#ifndef MAP_LOCKED
#define MAP_LOCKED 0
#endif
// This is the official SQL (and ordering of the fields) to load a Monitor.
// It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended
std::string load_monitor_sql =
"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, "
"`LinkedMonitors`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`,"
"`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings
"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, "
"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, "
"`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, "
"`OutputCodec`, `Encoder`, `OutputContainer`, "
"`RecordAudio`, "
"`Brightness`, `Contrast`, `Hue`, `Colour`, "
"`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`,"
"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, "
"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`,"
"`RTSPServer`, `RTSPStreamName`,"
"`SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`";
std::string CameraType_Strings[] = {
"Local",
"Remote",
"File",
"Ffmpeg",
"LibVLC",
"NVSOCKET",
"CURL",
2020-03-26 14:21:34 +08:00
"VNC",
};
2018-04-12 22:28:22 +08:00
Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) :
id(p_id),
shared_data(nullptr),
trigger_data(nullptr),
video_store_data(nullptr)
2017-11-28 22:50:09 +08:00
{
strncpy(name, p_name, sizeof(name)-1);
#if ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
map_fd = -1;
snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id);
#else // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
shm_id = 0;
#endif // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
mem_size = 0;
mem_ptr = nullptr;
2017-10-23 21:51:41 +08:00
last_event_id = 0;
2016-04-29 20:57:31 +08:00
last_state = IDLE;
2016-04-29 20:57:31 +08:00
last_connect_time = 0;
connected = false;
}
Monitor::MonitorLink::~MonitorLink() {
2016-04-29 20:57:31 +08:00
disconnect();
}
bool Monitor::MonitorLink::connect() {
if ( !last_connect_time || (time(nullptr) - last_connect_time) > 60 ) {
last_connect_time = time(nullptr);
2016-04-29 20:57:31 +08:00
mem_size = sizeof(SharedData) + sizeof(TriggerData);
2020-06-22 22:39:29 +08:00
Debug(1, "link.mem.size=%d", mem_size);
#if ZM_MEM_MAPPED
2020-06-22 22:39:29 +08:00
map_fd = open(mem_file, O_RDWR, (mode_t)0600);
2016-04-29 20:57:31 +08:00
if ( map_fd < 0 ) {
2020-06-22 22:39:29 +08:00
Debug(3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno));
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
while ( map_fd <= 2 ) {
int new_map_fd = dup(map_fd);
2020-06-22 22:39:29 +08:00
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
2016-04-29 20:57:31 +08:00
close(map_fd);
map_fd = new_map_fd;
}
struct stat map_stat;
2020-06-22 22:39:29 +08:00
if ( fstat(map_fd, &map_stat) < 0 ) {
Error("Can't stat linked memory map file %s: %s", mem_file, strerror(errno));
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
if ( map_stat.st_size == 0 ) {
2020-06-22 22:39:29 +08:00
Error("Linked memory map file %s is empty: %s", mem_file, strerror(errno));
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
} else if ( map_stat.st_size < mem_size ) {
2020-06-22 22:39:29 +08:00
Error("Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size);
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
2016-04-29 20:57:31 +08:00
if ( mem_ptr == MAP_FAILED ) {
2020-06-22 22:39:29 +08:00
Error("Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno));
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
#else // ZM_MEM_MAPPED
2021-02-06 00:04:02 +08:00
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700);
2016-04-29 20:57:31 +08:00
if ( shm_id < 0 ) {
2021-02-06 00:04:02 +08:00
Debug(3, "Can't shmget link memory: %s", strerror(errno));
2016-04-29 20:57:31 +08:00
connected = false;
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
2020-06-22 22:39:29 +08:00
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
if ( mem_ptr < (void *)0 ) {
2020-06-22 22:39:29 +08:00
Debug(3, "Can't shmat link memory: %s", strerror(errno));
2016-04-29 20:57:31 +08:00
connected = false;
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
#endif // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
2016-04-29 20:57:31 +08:00
if ( !shared_data->valid ) {
2020-06-22 22:39:29 +08:00
Debug(3, "Linked memory not initialised by capture daemon");
2016-04-29 20:57:31 +08:00
disconnect();
2020-06-22 22:39:29 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
2016-04-29 20:57:31 +08:00
last_state = shared_data->state;
2017-10-23 21:51:41 +08:00
last_event_id = shared_data->last_event_id;
2016-04-29 20:57:31 +08:00
connected = true;
2020-06-22 22:39:29 +08:00
return true;
2016-04-29 20:57:31 +08:00
}
2020-06-22 22:39:29 +08:00
return false;
} // end bool Monitor::MonitorLink::connect()
bool Monitor::MonitorLink::disconnect() {
2016-04-29 20:57:31 +08:00
if ( connected ) {
connected = false;
#if ZM_MEM_MAPPED
if ( mem_ptr > (void *)0 ) {
2016-04-29 20:57:31 +08:00
msync( mem_ptr, mem_size, MS_ASYNC );
munmap( mem_ptr, mem_size );
}
if ( map_fd >= 0 )
close( map_fd );
map_fd = -1;
#else // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
struct shmid_ds shm_data;
if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) {
Debug( 3, "Can't shmctl: %s", strerror(errno) );
return( false );
}
shm_id = 0;
if (shm_data.shm_nattch <= 1) {
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
2016-04-29 20:57:31 +08:00
}
}
if (shmdt(mem_ptr) < 0) {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
2016-04-29 20:57:31 +08:00
}
#endif // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
mem_size = 0;
mem_ptr = nullptr;
2016-04-29 20:57:31 +08:00
}
return true;
}
bool Monitor::MonitorLink::isAlarmed() {
if (!connected) {
return false;
2016-04-29 20:57:31 +08:00
}
return( shared_data->state == ALARM );
}
bool Monitor::MonitorLink::inAlarm() {
if (!connected) {
return false;
2016-04-29 20:57:31 +08:00
}
return( shared_data->state == ALARM || shared_data->state == ALERT );
}
2016-12-09 00:49:54 +08:00
bool Monitor::MonitorLink::hasAlarmed() {
if (shared_data->state == ALARM) {
return true;
2016-04-29 20:57:31 +08:00
}
last_event_id = shared_data->last_event_id;
return false;
}
Monitor::Monitor()
: id(0),
name(""),
server_id(0),
storage_id(0),
type(LOCAL),
function(NONE),
enabled(0),
2020-12-15 23:14:19 +08:00
decoding_enabled(0),
//protocol
//method
//options
//host
//port
//user
//pass
//path
//device
palette(0),
channel(0),
format(0),
width(0),
height(0),
//v4l_multi_buffer
//v4l_captures_per_frame
orientation(ROTATE_0),
deinterlacing(0),
deinterlacing_value(0),
decoder_hwaccel_name(""),
decoder_hwaccel_device(""),
videoRecording(0),
rtsp_describe(0),
savejpegs(0),
colours(0),
videowriter(DISABLED),
encoderparams(""),
output_codec(0),
encoder(""),
output_container(""),
imagePixFormat(AV_PIX_FMT_NONE),
subpixelorder(0),
record_audio(0),
//event_prefix
//label_format
label_coord(Coord(0,0)),
label_size(0),
image_buffer_count(0),
warmup_count(0),
pre_event_count(0),
post_event_count(0),
stream_replay_buffer(0),
section_length(0),
min_section_length(0),
adaptive_skip(false),
frame_skip(0),
motion_frame_skip(0),
analysis_fps_limit(0),
analysis_update_delay(0),
capture_delay(0),
alarm_capture_delay(0),
alarm_frame_count(0),
alert_to_alarm_frame_count(0),
fps_report_interval(0),
ref_blend_perc(0),
alarm_ref_blend_perc(0),
track_motion(0),
signal_check_points(0),
signal_check_colour(0),
embed_exif(0),
rtsp_server(0),
rtsp_streamname(""),
capture_max_fps(0),
purpose(QUERY),
last_camera_bytes(0),
event_count(0),
image_count(0),
last_capture_image_count(0),
analysis_image_count(0),
motion_frame_count(0),
last_motion_frame_count(0),
ready_count(0),
first_alarm_count(0),
last_alarm_count(0),
last_signal(false),
last_section_mod(0),
buffer_count(0),
state(IDLE),
start_time(0),
last_fps_time(0),
last_analysis_fps_time(0),
auto_resume_time(0),
2016-04-29 20:57:31 +08:00
last_motion_score(0),
event_close_mode(CLOSE_IDLE),
#if ZM_MEM_MAPPED
map_fd(-1),
mem_file(""),
#else // ZM_MEM_MAPPED
shm_id(-1),
#endif // ZM_MEM_MAPPED
mem_size(0),
mem_ptr(nullptr),
shared_data(nullptr),
trigger_data(nullptr),
video_store_data(nullptr),
shared_timestamps(nullptr),
shared_images(nullptr),
video_fifo(nullptr),
audio_fifo(nullptr),
camera(nullptr),
event(nullptr),
storage(nullptr),
videoStore(nullptr),
analysis_it(nullptr),
n_zones(0),
zones(nullptr),
privacy_bitmask(nullptr),
n_linked_monitors(0),
linked_monitors(nullptr)
{
2016-04-29 20:57:31 +08:00
if ( strcmp(config.event_close_mode, "time") == 0 )
event_close_mode = CLOSE_TIME;
else if ( strcmp(config.event_close_mode, "alarm") == 0 )
event_close_mode = CLOSE_ALARM;
else
event_close_mode = CLOSE_IDLE;
event = 0;
last_section_mod = 0;
adaptive_skip = true;
videoStore = nullptr;
} // Monitor::Monitor
/*
std::string load_monitor_sql =
2020-12-15 23:14:19 +08:00
"SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, "
"AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS,"
"Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings
"Protocol, Method, Options, User, Pass, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, "
"SaveJPEGs, VideoWriter, EncoderParameters,
"OutputCodec, Encoder, OutputContainer,"
"RecordAudio, "
"Brightness, Contrast, Hue, Colour, "
"EventPrefix, LabelFormat, LabelX, LabelY, LabelSize,"
"ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, "
"SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, "
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif,"
"`RTSPServer`,`RTSPStreamName`,
"SignalCheckPoints, SignalCheckColour FROM Monitors";
*/
void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
purpose = p;
int col = 0;
id = atoi(dbrow[col]); col++;
strncpy(name, dbrow[col], sizeof(name)-1); col++;
server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
storage_id = atoi(dbrow[col]); col++;
if ( storage )
delete storage;
storage = new Storage(storage_id);
if ( ! strcmp(dbrow[col], "Local") ) {
type = LOCAL;
} else if ( ! strcmp(dbrow[col], "Ffmpeg") ) {
type = FFMPEG;
} else if ( ! strcmp(dbrow[col], "Remote") ) {
type = REMOTE;
} else if ( ! strcmp(dbrow[col], "File") ) {
type = FILE;
} else if ( ! strcmp(dbrow[col], "NVSocket") ) {
type = NVSOCKET;
} else if ( ! strcmp(dbrow[col], "Libvlc") ) {
type = LIBVLC;
} else if ( ! strcmp(dbrow[col], "cURL") ) {
type = CURL;
2021-02-18 05:39:16 +08:00
} else if ( ! strcmp(dbrow[col], "VNC") ) {
type = VNC;
} else {
Fatal("Bogus monitor type '%s' for monitor %d", dbrow[col], id);
}
Debug(1, "Have camera type %s", CameraType_Strings[type].c_str());
col++;
function = (Function)atoi(dbrow[col]); col++;
enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
2020-12-15 23:14:19 +08:00
decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
ReloadLinkedMonitors(dbrow[col]); col++;
analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++;
analysis_update_delay = strtoul(dbrow[col++], nullptr, 0);
capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++;
alarm_capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++;
if ( dbrow[col] )
strncpy(device, dbrow[col], sizeof(device)-1);
else
device[0] = 0;
col++;
channel = atoi(dbrow[col]); col++;
format = atoi(dbrow[col]); col++;
v4l_multi_buffer = config.v4l_multi_buffer;
if ( dbrow[col] ) {
if (*dbrow[col] == '0' ) {
v4l_multi_buffer = false;
} else if ( *dbrow[col] == '1' ) {
v4l_multi_buffer = true;
}
}
col++;
2016-04-29 20:57:31 +08:00
v4l_captures_per_frame = 0;
if ( dbrow[col] ) {
v4l_captures_per_frame = atoi(dbrow[col]);
} else {
v4l_captures_per_frame = config.captures_per_frame;
}
col++;
protocol = dbrow[col] ? dbrow[col] : ""; col++;
method = dbrow[col] ? dbrow[col] : ""; col++;
options = dbrow[col] ? dbrow[col] : ""; col++;
user = dbrow[col] ? dbrow[col] : ""; col++;
pass = dbrow[col] ? dbrow[col] : ""; col++;
host = dbrow[col] ? dbrow[col] : ""; col++;
port = dbrow[col] ? dbrow[col] : ""; col++;
path = dbrow[col] ? dbrow[col] : ""; col++;
camera_width = atoi(dbrow[col]); col++;
camera_height = atoi(dbrow[col]); col++;
colours = atoi(dbrow[col]); col++;
palette = atoi(dbrow[col]); col++;
orientation = (Orientation)atoi(dbrow[col]); col++;
width = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_height : camera_width;
height = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_width : camera_height;
deinterlacing = atoi(dbrow[col]); col++;
deinterlacing_value = deinterlacing & 0xff;
decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++;
decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++;
rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++;
savejpegs = atoi(dbrow[col]); col++;
videowriter = (VideoWriter)atoi(dbrow[col]); col++;
encoderparams = dbrow[col] ? dbrow[col] : ""; col++;
/* Parse encoder parameters */
ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec);
output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
encoder = dbrow[col] ? dbrow[col] : ""; col++;
output_container = dbrow[col] ? dbrow[col] : ""; col++;
record_audio = (*dbrow[col] != '0'); col++;
brightness = atoi(dbrow[col]); col++;
contrast = atoi(dbrow[col]); col++;
hue = atoi(dbrow[col]); col++;
colour = atoi(dbrow[col]); col++;
if ( dbrow[col] )
strncpy(event_prefix, dbrow[col], sizeof(event_prefix)-1);
else
event_prefix[0] = 0;
col++;
if ( dbrow[col] )
strncpy(label_format, dbrow[col], sizeof(label_format)-1);
else
label_format[0] = 0;
col++;
2016-04-29 20:57:31 +08:00
// Change \n to actual line feeds
char *token_ptr = label_format;
const char *token_string = "\n";
while ( ( token_ptr = strstr(token_ptr, token_string) ) ) {
2016-12-09 00:49:54 +08:00
if ( *(token_ptr+1) ) {
2016-04-29 20:57:31 +08:00
*token_ptr = '\n';
token_ptr++;
strcpy(token_ptr, token_ptr+1);
2016-12-09 00:49:54 +08:00
} else {
2016-04-29 20:57:31 +08:00
*token_ptr = '\0';
break;
}
}
label_coord = Coord(atoi(dbrow[col]), atoi(dbrow[col+1])); col += 2;
label_size = atoi(dbrow[col]); col++;
2016-04-29 20:57:31 +08:00
image_buffer_count = atoi(dbrow[col]); col++;
warmup_count = atoi(dbrow[col]); col++;
pre_event_count = atoi(dbrow[col]); col++;
packetqueue.setMaxVideoPackets(pre_event_count);
post_event_count = atoi(dbrow[col]); col++;
stream_replay_buffer = atoi(dbrow[col]); col++;
alarm_frame_count = atoi(dbrow[col]); col++;
2016-04-29 20:57:31 +08:00
if ( alarm_frame_count < 1 )
alarm_frame_count = 1;
else if ( alarm_frame_count > MAX_PRE_ALARM_FRAMES )
alarm_frame_count = MAX_PRE_ALARM_FRAMES;
pre_event_buffer_count = pre_event_count + alarm_frame_count + warmup_count - 1;
2016-04-29 20:57:31 +08:00
section_length = atoi(dbrow[col]); col++;
min_section_length = atoi(dbrow[col]); col++;
frame_skip = atoi(dbrow[col]); col++;
motion_frame_skip = atoi(dbrow[col]); col++;
fps_report_interval = atoi(dbrow[col]); col++;
ref_blend_perc = atoi(dbrow[col]); col++;
alarm_ref_blend_perc = atoi(dbrow[col]); col++;
track_motion = atoi(dbrow[col]); col++;
2016-04-29 20:57:31 +08:00
embed_exif = (*dbrow[col] != '0'); col++;
rtsp_server = (*dbrow[col] != '0'); col++;
rtsp_streamname = dbrow[col]; col++;
signal_check_points = atoi(dbrow[col]); col++;
signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++;
2017-10-28 11:36:49 +08:00
2017-12-02 03:33:51 +08:00
// How many frames we need to have before we start analysing
ready_count = std::max(warmup_count, pre_event_count);
2016-04-29 20:57:31 +08:00
last_alarm_count = 0;
state = IDLE;
last_signal = true; // Defaulting to having signal so that we don't get a signal change on the first frame.
// Instead initial failure to capture will cause a loss of signal change which I think makes more sense.
uint64_t image_size = width * height * colours;
2016-04-29 20:57:31 +08:00
if ( strcmp(config.event_close_mode, "time") == 0 )
2016-04-29 20:57:31 +08:00
event_close_mode = CLOSE_TIME;
else if ( strcmp(config.event_close_mode, "alarm") == 0 )
2016-04-29 20:57:31 +08:00
event_close_mode = CLOSE_ALARM;
else
event_close_mode = CLOSE_IDLE;
mem_size = sizeof(SharedData)
+ sizeof(TriggerData)
+ sizeof(VideoStoreData) //Information to pass back to the capture process
+ (image_buffer_count * sizeof(struct timeval))
+ (image_buffer_count * width * height * colours)
+ 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */
2016-04-29 20:57:31 +08:00
Debug(1, "mem.size(%d) SharedData=%d TriggerData=%d VideoStoreData=%d timestamps=%d images=%dx%d = %" PRId64 " total=%" PRId64,
sizeof(mem_size),
sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData),
(image_buffer_count*sizeof(struct timeval)),
image_buffer_count, image_size, (image_buffer_count*image_size),
mem_size);
2016-04-29 20:57:31 +08:00
Zone **zones = 0;
int n_zones = Zone::Load(this, zones);
this->AddZones(n_zones, zones);
this->AddPrivacyBitmask(zones);
2017-01-18 22:31:08 +08:00
// Should maybe store this for later use
std::string monitor_dir = stringtf("%s/%d", storage->Path(), id);
LoadCamera();
2016-05-07 00:39:10 +08:00
if ( purpose != QUERY ) {
if ( mkdir(monitor_dir.c_str(), 0755) && ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno));
2017-08-12 00:03:37 +08:00
}
// Do this here to save a few cycles with all the comparisons
decoding_enabled = !(
( function == RECORD or function == NODECT )
and
( savejpegs == 0 )
and
( videowriter == PASSTHROUGH )
and
!decoding_enabled
);
Debug(1, "Decoding enabled: %d", decoding_enabled);
if ( config.record_diag_images ) {
if ( config.record_diag_images_fifo ) {
diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
diag_path_delta = stringtf("%s/diagpipe-d-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
Fifo::fifo_create_if_missing(diag_path_ref.c_str());
Fifo::fifo_create_if_missing(diag_path_delta.c_str());
} else {
diag_path_ref = stringtf("%s/%d/diag-r.jpg", storage->Path(), id);
diag_path_delta = stringtf("%s/%d/diag-d.jpg", storage->Path(), id);
}
}
} // end if purpose
Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones);
} // Monitor::Load
2016-05-07 00:39:10 +08:00
void Monitor::LoadCamera() {
if (camera)
return;
2016-04-29 20:57:31 +08:00
switch (type) {
case LOCAL: {
#if ZM_HAS_V4L
int extras = (deinterlacing >> 24) & 0xff;
camera = ZM::make_unique<LocalCamera>(this,
device,
channel,
format,
v4l_multi_buffer,
v4l_captures_per_frame,
method,
camera_width,
camera_height,
colours,
palette,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio,
extras
);
#else
Fatal("Not compiled with local v4l camera support");
#endif
break;
2016-04-29 20:57:31 +08:00
}
case REMOTE: {
if (protocol == "http") {
camera = ZM::make_unique<RemoteCameraHttp>(this,
method,
host,
port,
path,
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
}
#if HAVE_LIBAVFORMAT
else if (protocol == "rtsp") {
camera = ZM::make_unique<RemoteCameraRtsp>(this,
method,
host, // Host
port, // Port
path, // Path
camera_width,
camera_height,
rtsp_describe,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
}
#endif // HAVE_LIBAVFORMAT
else {
Error("Unexpected remote camera protocol '%s'", protocol.c_str());
}
break;
}
case FILE: {
camera = ZM::make_unique<FileCamera>(this,
path.c_str(),
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
break;
}
#if HAVE_LIBAVFORMAT
case FFMPEG: {
camera = ZM::make_unique<FfmpegCamera>(this,
path,
method,
options,
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio,
decoder_hwaccel_name,
decoder_hwaccel_device
);
break;
}
#endif // HAVE_LIBAVFORMAT
case NVSOCKET: {
camera = ZM::make_unique<RemoteCameraNVSocket>(this,
host.c_str(),
port.c_str(),
path.c_str(),
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
2017-10-23 21:51:41 +08:00
);
break;
}
case LIBVLC: {
#if HAVE_LIBVLC
camera = ZM::make_unique<LibvlcCamera>(this,
path.c_str(),
method,
options,
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
#else // HAVE_LIBVLC
Error("You must have vlc libraries installed to use vlc cameras for monitor %d", id);
#endif // HAVE_LIBVLC
break;
}
case CURL: {
#if HAVE_LIBCURL
camera = ZM::make_unique<cURLCamera>(this,
path.c_str(),
user.c_str(),
pass.c_str(),
camera_width,
camera_height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
#else // HAVE_LIBCURL
Error("You must have libcurl installed to use ffmpeg cameras for monitor %d", id);
#endif // HAVE_LIBCURL
break;
}
case VNC: {
#if HAVE_LIBVNC
camera = ZM::make_unique<VncCamera>(this,
host.c_str(),
port.c_str(),
user.c_str(),
pass.c_str(),
width,
height,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
#else // HAVE_LIBVNC
Fatal("You must have libvnc installed to use VNC cameras for monitor id %d", id);
#endif // HAVE_LIBVNC
break;
}
default: {
Fatal("Tried to load unsupported camera type %d for monitor %u", int(type), id);
break;
}
}
}
2016-04-29 20:57:31 +08:00
std::shared_ptr<Monitor> Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) {
std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", p_id);
2016-04-29 20:57:31 +08:00
zmDbRow dbrow;
if ( !dbrow.fetch(sql.c_str()) ) {
Error("Can't use query result: %s", mysql_error(&dbconn));
return nullptr;
}
std::shared_ptr<Monitor> monitor = std::make_shared<Monitor>();
monitor->Load(dbrow.mysql_row(), load_zones, purpose);
2016-04-29 20:57:31 +08:00
return monitor;
}
2014-06-27 02:54:47 +08:00
bool Monitor::connect() {
Debug(3, "Connecting to monitor. Purpose is %d", purpose);
2014-06-27 02:54:47 +08:00
#if ZM_MEM_MAPPED
2018-12-28 00:50:43 +08:00
snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id);
if (purpose != CAPTURE) {
map_fd = open(mem_file, O_RDWR);
} else {
map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0660);
}
if (map_fd < 0) {
Error("Can't open memory map file %s: %s", mem_file, strerror(errno));
return false;
2017-08-12 00:03:37 +08:00
} else {
2018-12-28 00:50:43 +08:00
Debug(3, "Success opening mmap file at (%s)", mem_file);
2017-08-12 00:03:37 +08:00
}
2016-04-29 20:57:31 +08:00
struct stat map_stat;
if (fstat(map_fd, &map_stat) < 0) {
Error("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno));
close(map_fd);
2021-02-19 02:43:24 +08:00
map_fd = -1;
return false;
}
2017-08-12 00:03:37 +08:00
if (map_stat.st_size != mem_size) {
if (purpose == CAPTURE) {
2017-08-12 00:03:37 +08:00
// Allocate the size
if (ftruncate(map_fd, mem_size) < 0) {
2021-02-19 02:43:24 +08:00
Error("Can't extend memory map file %s to %d bytes: %s", mem_file, mem_size, strerror(errno));
close(map_fd);
map_fd = -1;
return false;
2017-08-12 00:03:37 +08:00
}
} else if (map_stat.st_size == 0) {
2018-12-28 00:50:43 +08:00
Error("Got empty memory map file size %ld, is the zmc process for this monitor running?", map_stat.st_size, mem_size);
close(map_fd);
map_fd = -1;
2017-08-12 00:03:37 +08:00
return false;
} else {
2018-12-28 00:50:43 +08:00
Error("Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size);
close(map_fd);
map_fd = -1;
2017-08-12 00:03:37 +08:00
return false;
2016-04-29 20:57:31 +08:00
}
} // end if map_stat.st_size != mem_size
2017-08-12 00:03:37 +08:00
2018-12-28 00:50:43 +08:00
Debug(3, "MMap file size is %ld", map_stat.st_size);
#ifdef MAP_LOCKED
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0);
if (mem_ptr == MAP_FAILED) {
if (errno == EAGAIN) {
2018-12-28 00:50:43 +08:00
Debug(1, "Unable to map file %s (%d bytes) to locked memory, trying unlocked", mem_file, mem_size);
#endif
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
2018-12-28 00:50:43 +08:00
Debug(1, "Mapped file %s (%d bytes) to unlocked memory", mem_file, mem_size);
#ifdef MAP_LOCKED
2017-08-12 00:03:37 +08:00
} else {
2018-12-28 00:50:43 +08:00
Error("Unable to map file %s (%d bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno));
2016-04-29 20:57:31 +08:00
}
2017-08-12 00:03:37 +08:00
}
#endif
if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) {
Error("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno);
close(map_fd);
map_fd = -1;
mem_ptr = nullptr;
return false;
2016-04-29 20:57:31 +08:00
}
2014-06-27 02:54:47 +08:00
#else // ZM_MEM_MAPPED
2018-12-28 00:50:43 +08:00
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700);
if (shm_id < 0) {
2018-12-28 00:50:43 +08:00
Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno));
2016-04-29 20:57:31 +08:00
}
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
if (mem_ptr < (void *)0) {
2018-12-28 00:50:43 +08:00
Fatal("Can't shmat: %s", strerror(errno));
2016-04-29 20:57:31 +08:00
}
2014-06-27 02:54:47 +08:00
#endif // ZM_MEM_MAPPED
2016-04-29 20:57:31 +08:00
shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData));
2017-11-22 08:55:40 +08:00
shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData));
shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval)));
if (((unsigned long)shared_images % 64) != 0) {
/* Align images buffer to nearest 64 byte boundary */
2021-02-19 02:43:24 +08:00
Debug(3, "Aligning shared memory images to the next 64 byte boundary");
shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64)));
2016-04-29 20:57:31 +08:00
}
if (!camera) LoadCamera();
2018-12-28 00:50:43 +08:00
Debug(3, "Allocating %d image buffers", image_buffer_count);
2017-11-14 01:14:57 +08:00
image_buffer = new ZMPacket[image_buffer_count];
for (int32_t i = 0; i < image_buffer_count; i++) {
2017-11-22 08:55:40 +08:00
image_buffer[i].image_index = i;
2018-02-24 08:01:42 +08:00
image_buffer[i].timestamp = &(shared_timestamps[i]);
2020-08-06 23:57:35 +08:00
image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]));
2018-02-24 08:01:42 +08:00
image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */
}
if (deinterlacing_value == 4) {
2018-02-24 08:01:42 +08:00
/* Four field motion adaptive deinterlacing in use */
/* Allocate a buffer for the next image */
next_buffer.image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
}
2017-11-22 08:55:40 +08:00
if (purpose == CAPTURE) {
memset(mem_ptr, 0, mem_size);
shared_data->size = sizeof(SharedData);
shared_data->active = enabled;
shared_data->signal = false;
shared_data->capture_fps = 0.0;
shared_data->analysis_fps = 0.0;
shared_data->state = IDLE;
shared_data->last_write_index = image_buffer_count;
shared_data->last_read_index = image_buffer_count;
shared_data->last_write_time = 0;
shared_data->last_event_id = 0;
shared_data->action = (Action)0;
shared_data->brightness = -1;
shared_data->hue = -1;
shared_data->colour = -1;
shared_data->contrast = -1;
shared_data->alarm_x = -1;
shared_data->alarm_y = -1;
shared_data->format = camera->SubpixelOrder();
shared_data->imagesize = camera->ImageSize();
shared_data->alarm_cause[0] = 0;
shared_data->video_fifo_path[0] = 0;
shared_data->audio_fifo_path[0] = 0;
shared_data->last_frame_score = 0;
shared_data->audio_frequency = -1;
shared_data->audio_channels = -1;
trigger_data->size = sizeof(TriggerData);
trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL;
trigger_data->trigger_score = 0;
trigger_data->trigger_cause[0] = 0;
trigger_data->trigger_text[0] = 0;
trigger_data->trigger_showtext[0] = 0;
video_store_data->recording = (struct timeval){0};
// Uh, why nothing? Why not nullptr?
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing");
video_store_data->size = sizeof(VideoStoreData);
shared_data->valid = true;
2021-02-19 02:43:24 +08:00
} else if ( !shared_data->valid ) {
Error("Shared data not initialised by capture daemon for monitor %s", name);
return false;
2018-02-24 08:01:42 +08:00
}
// We set these here because otherwise the first fps calc is meaningless
struct timeval now;
gettimeofday(&now, nullptr);
double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec);
last_fps_time = now_double;
last_analysis_fps_time = now_double;
Debug(3, "Success connecting");
2016-04-29 20:57:31 +08:00
return true;
} // Monitor::connect
2014-06-27 02:54:47 +08:00
bool Monitor::disconnect() {
2021-02-19 02:43:24 +08:00
if (mem_ptr == nullptr) {
Debug(1, "Already disconnected");
return true;
2021-02-19 02:43:24 +08:00
}
#if ZM_MEM_MAPPED
2021-02-16 23:32:07 +08:00
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
if (map_fd >= 0) close(map_fd);
map_fd = -1;
mem_ptr = nullptr;
shared_data = nullptr;
2021-02-19 02:43:24 +08:00
if (purpose == CAPTURE and (unlink(mem_file) < 0) ) {
Warning("Can't unlink '%s': %s", mem_file, strerror(errno));
}
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
2021-02-16 23:32:07 +08:00
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
shm_id = 0;
2021-02-19 02:43:24 +08:00
if ((shm_data.shm_nattch <= 1) and (shmctl(shm_id, IPC_RMID, 0) < 0)) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
2021-02-16 23:32:07 +08:00
if (shmdt(mem_ptr) < 0) {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
}
#endif // ZM_MEM_MAPPED
2021-02-16 23:32:07 +08:00
if (image_buffer) {
2021-02-18 02:38:52 +08:00
for ( int32_t i = 0; i < image_buffer_count; i++ ) {
// We delete the image because it is an object pointing to space that won't be free'd.
delete image_buffer[i].image;
image_buffer[i].image = nullptr;
// We don't delete the timestamp because it is just a pointer to shared mem.
image_buffer[i].timestamp = nullptr;
}
delete[] image_buffer;
image_buffer = nullptr;
}
return true;
} // end bool Monitor::disconnect()
2016-06-22 02:02:36 +08:00
Monitor::~Monitor() {
Close();
2021-02-16 23:32:07 +08:00
if (mem_ptr != nullptr) {
if (purpose != QUERY) {
2016-04-29 20:57:31 +08:00
shared_data->state = state = IDLE;
shared_data->last_read_index = image_buffer_count;
shared_data->last_read_time = 0;
shared_data->valid = false;
2020-07-21 09:09:07 +08:00
memset(mem_ptr, 0, mem_size);
if ( (deinterlacing & 0xff) == 4 ) {
delete next_buffer.image;
delete next_buffer.timestamp;
}
2021-02-16 23:32:07 +08:00
} // end if purpose != query
disconnect();
2021-02-16 23:32:07 +08:00
} // end if mem_ptr
2021-02-16 23:32:07 +08:00
if (analysis_it) {
packetqueue.free_it(analysis_it);
analysis_it = nullptr;
}
for ( int i = 0; i < n_zones; i++ ) {
delete zones[i];
}
delete[] zones;
delete storage;
2021-02-16 23:32:07 +08:00
if (n_linked_monitors) {
2021-02-01 00:57:43 +08:00
for ( int i=0; i < n_linked_monitors; i++ ) {
delete linked_monitors[i];
}
delete[] linked_monitors;
linked_monitors = nullptr;
}
if (video_fifo) delete video_fifo;
if (audio_fifo) delete audio_fifo;
} // end Monitor::~Monitor()
void Monitor::AddZones(int p_n_zones, Zone *p_zones[]) {
for ( int i=0; i < n_zones; i++ )
2016-04-29 20:57:31 +08:00
delete zones[i];
delete[] zones;
n_zones = p_n_zones;
zones = p_zones;
}
void Monitor::AddPrivacyBitmask(Zone *p_zones[]) {
2017-12-13 01:52:30 +08:00
if ( privacy_bitmask ) {
2016-04-29 20:57:31 +08:00
delete[] privacy_bitmask;
privacy_bitmask = nullptr;
2017-12-13 01:52:30 +08:00
}
Image *privacy_image = nullptr;
2016-04-29 20:57:31 +08:00
for ( int i=0; i < n_zones; i++ ) {
2016-06-22 02:02:36 +08:00
if ( p_zones[i]->IsPrivacy() ) {
if ( !privacy_image ) {
privacy_image = new Image(width, height, 1, ZM_SUBPIX_ORDER_NONE);
2016-04-29 20:57:31 +08:00
privacy_image->Clear();
}
privacy_image->Fill(0xff, p_zones[i]->GetPolygon());
privacy_image->Outline(0xff, p_zones[i]->GetPolygon());
2016-04-29 20:57:31 +08:00
}
} // end foreach zone
if ( privacy_image )
privacy_bitmask = privacy_image->Buffer();
}
int Monitor::GetImage(int32_t index, int scale) {
2016-06-22 02:02:36 +08:00
if ( index < 0 || index > image_buffer_count ) {
2016-04-29 20:57:31 +08:00
index = shared_data->last_write_index;
}
2016-06-22 02:02:36 +08:00
if ( index != image_buffer_count ) {
2016-04-29 20:57:31 +08:00
Image *image;
// If we are going to be modifying the snapshot before writing, then we need to copy it
if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) {
2017-11-14 01:14:57 +08:00
ZMPacket *snap = &image_buffer[index];
alarm_image.Assign(*snap->image);
2016-04-29 20:57:31 +08:00
if ( scale != ZM_SCALE_BASE ) {
2019-05-07 00:16:06 +08:00
alarm_image.Scale(scale);
2016-04-29 20:57:31 +08:00
}
if ( !config.timestamp_on_capture ) {
2019-05-07 00:16:06 +08:00
TimestampImage(&alarm_image, snap->timestamp);
2016-04-29 20:57:31 +08:00
}
image = &alarm_image;
} else {
image = image_buffer[index].image;
}
static char filename[PATH_MAX];
2019-05-07 00:16:06 +08:00
snprintf(filename, sizeof(filename), "Monitor%d.jpg", id);
image->WriteJpeg(filename);
2016-06-22 02:02:36 +08:00
} else {
2019-05-07 00:16:06 +08:00
Error("Unable to generate image, no images in buffer");
2016-04-29 20:57:31 +08:00
}
2019-05-07 00:16:06 +08:00
return 0;
}
ZMPacket *Monitor::getSnapshot(int index) const {
2017-11-22 08:55:40 +08:00
if ( (index < 0) || (index > image_buffer_count) ) {
2016-04-29 20:57:31 +08:00
index = shared_data->last_write_index;
}
2017-11-22 08:55:40 +08:00
return &image_buffer[index];
2016-04-29 20:57:31 +08:00
return nullptr;
2017-11-22 08:55:40 +08:00
}
2016-04-29 20:57:31 +08:00
struct timeval Monitor::GetTimestamp(int index) const {
ZMPacket *packet = getSnapshot(index);
2017-11-22 08:55:40 +08:00
if ( packet )
return *packet->timestamp;
2016-04-29 20:57:31 +08:00
2017-11-22 08:55:40 +08:00
static struct timeval null_tv = { 0, 0 };
return null_tv;
}
2016-06-22 02:02:36 +08:00
unsigned int Monitor::GetLastReadIndex() const {
2021-02-18 02:38:52 +08:00
return ( shared_data->last_read_index != image_buffer_count ? shared_data->last_read_index : -1 );
}
2016-06-22 02:02:36 +08:00
unsigned int Monitor::GetLastWriteIndex() const {
2021-02-18 02:38:52 +08:00
return ( shared_data->last_write_index != image_buffer_count ? shared_data->last_write_index : -1 );
}
uint64_t Monitor::GetLastEventId() const {
return shared_data->last_event_id;
}
2017-11-25 04:37:50 +08:00
// This function is crap.
2016-06-22 02:02:36 +08:00
double Monitor::GetFPS() const {
return get_capture_fps();
2018-01-19 00:39:33 +08:00
// last_write_index is the last capture index. It starts as == image_buffer_count so that the first asignment % image_buffer_count = 0;
2021-02-18 02:38:52 +08:00
int32_t index1 = shared_data->last_write_index;
2017-12-01 05:10:30 +08:00
if ( index1 >= image_buffer_count ) {
2017-11-25 04:37:50 +08:00
// last_write_index only has this value on startup before capturing anything.
return 0.0;
2016-04-29 20:57:31 +08:00
}
2017-12-01 05:10:30 +08:00
Debug(2, "index1(%d)", index1);
2017-11-14 01:14:57 +08:00
ZMPacket *snap1 = &image_buffer[index1];
2017-11-22 08:55:40 +08:00
if ( !snap1->timestamp->tv_sec ) {
2017-11-25 04:37:50 +08:00
// This should be impossible
2018-02-24 08:01:42 +08:00
Warning("Impossible situation. No timestamp on captured image index was %d, image-buffer_count was (%d)", index1, image_buffer_count);
2017-11-25 04:37:50 +08:00
return 0.0;
2016-04-29 20:57:31 +08:00
}
struct timeval time1 = *snap1->timestamp;
2021-02-18 02:38:52 +08:00
int32_t fps_image_count = image_buffer_count;
2017-11-18 03:30:38 +08:00
2021-02-18 02:38:52 +08:00
int32_t index2 = (index1+1)%image_buffer_count;
2017-12-01 05:10:30 +08:00
Debug(2, "index2(%d)", index2);
ZMPacket *snap2 = &image_buffer[index2];
2017-11-25 04:37:50 +08:00
// the timestamp pointers are initialized on connection, so that's redundant
// tv_sec is probably only zero during the first loop of capturing, so this basically just counts the unused images.
2018-01-19 00:39:33 +08:00
// The problem is that there is no locking, and we set the timestamp before we set last_write_index,
// so there is a small window where the next image can have a timestamp in the future
while ( !snap2->timestamp->tv_sec || tvDiffSec(*snap2->timestamp, *snap1->timestamp) < 0 ) {
2016-06-22 02:02:36 +08:00
if ( index1 == index2 ) {
2018-01-19 00:39:33 +08:00
// All images are uncaptured
2017-11-25 04:37:50 +08:00
return 0.0;
2016-04-29 20:57:31 +08:00
}
index2 = (index2+1)%image_buffer_count;
2017-11-22 08:55:40 +08:00
snap2 = &image_buffer[ index2 ];
2017-12-02 03:33:51 +08:00
fps_image_count--;
2016-04-29 20:57:31 +08:00
}
struct timeval time2 = *snap2->timestamp;
double time_diff = tvDiffSec( time2, time1 );
2019-01-22 02:00:10 +08:00
if ( ! time_diff ) {
Error("No diff between time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
2019-01-22 02:00:10 +08:00
return 0.0;
}
2017-12-02 03:33:51 +08:00
double curr_fps = fps_image_count/time_diff;
2016-04-29 20:57:31 +08:00
2016-06-22 02:02:36 +08:00
if ( curr_fps < 0.0 ) {
Error("Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
2017-11-25 04:37:50 +08:00
return 0.0;
2017-12-13 03:37:02 +08:00
} else {
Debug(2, "GetFPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d",
curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count);
2016-04-29 20:57:31 +08:00
}
2017-11-25 04:37:50 +08:00
return curr_fps;
}
2018-02-13 18:26:30 +08:00
/* I think this returns the # of micro seconds that we should sleep in order to maintain the desired analysis rate */
2016-06-22 02:02:36 +08:00
useconds_t Monitor::GetAnalysisRate() {
double capture_fps = get_capture_fps();
2017-11-22 08:55:40 +08:00
if ( !analysis_fps_limit ) {
2019-04-16 00:55:28 +08:00
return 0;
2017-11-22 08:55:40 +08:00
} else if ( analysis_fps_limit > capture_fps ) {
if ( last_fps_time != last_analysis_fps_time ) {
// At startup they are equal, should never be equal again
Warning("Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps_limit, capture_fps);
}
2019-04-16 00:55:28 +08:00
return 0;
} else if ( capture_fps ) {
2017-11-22 08:55:40 +08:00
return( ( 1000000 / analysis_fps_limit ) - ( 1000000 / capture_fps ) );
2016-04-29 20:57:31 +08:00
}
return 0;
}
2016-06-22 02:02:36 +08:00
void Monitor::UpdateAdaptiveSkip() {
if ( config.opt_adaptive_skip ) {
double capturing_fps = get_capture_fps();
double analysis_fps = get_analysis_fps();
2016-06-22 02:02:36 +08:00
if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) {
2019-04-16 00:55:28 +08:00
Info("Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps);
2016-04-29 20:57:31 +08:00
adaptive_skip = false;
2016-06-22 02:02:36 +08:00
} else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) {
2019-04-16 00:55:28 +08:00
Info("Enabling adaptive skip feature");
2016-04-29 20:57:31 +08:00
adaptive_skip = true;
}
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
adaptive_skip = false;
}
}
2016-06-22 02:02:36 +08:00
void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) {
trigger_data->trigger_state = TriggerState::TRIGGER_ON;
2016-04-29 20:57:31 +08:00
trigger_data->trigger_score = force_score;
2019-04-16 00:55:28 +08:00
strncpy(trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause)-1);
strncpy(trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text)-1);
}
2016-06-22 02:02:36 +08:00
void Monitor::ForceAlarmOff() {
trigger_data->trigger_state = TriggerState::TRIGGER_OFF;
}
2016-06-22 02:02:36 +08:00
void Monitor::CancelForced() {
trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL;
}
2016-06-22 02:02:36 +08:00
void Monitor::actionReload() {
2016-04-29 20:57:31 +08:00
shared_data->action |= RELOAD;
}
2016-06-22 02:02:36 +08:00
void Monitor::actionEnable() {
2016-04-29 20:57:31 +08:00
shared_data->action |= RELOAD;
char sql[ZM_SQL_SML_BUFSIZ];
2019-08-18 02:36:52 +08:00
snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %d", id);
zmDbDo(sql);
}
2016-06-22 02:02:36 +08:00
void Monitor::actionDisable() {
2016-04-29 20:57:31 +08:00
shared_data->action |= RELOAD;
char sql[ZM_SQL_SML_BUFSIZ];
2019-08-19 09:32:19 +08:00
snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %d", id);
zmDbDo(sql);
}
2016-06-22 02:02:36 +08:00
void Monitor::actionSuspend() {
2016-04-29 20:57:31 +08:00
shared_data->action |= SUSPEND;
}
2016-06-22 02:02:36 +08:00
void Monitor::actionResume() {
2016-04-29 20:57:31 +08:00
shared_data->action |= RESUME;
}
2019-04-16 00:55:28 +08:00
int Monitor::actionBrightness(int p_brightness) {
2016-06-22 02:02:36 +08:00
if ( purpose != CAPTURE ) {
if ( p_brightness >= 0 ) {
2016-04-29 20:57:31 +08:00
shared_data->brightness = p_brightness;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & SET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to set brightness");
return -1;
2016-04-29 20:57:31 +08:00
}
}
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & GET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to get brightness");
return -1;
2016-04-29 20:57:31 +08:00
}
}
}
2019-04-16 00:55:28 +08:00
return shared_data->brightness;
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
return camera->Brightness(p_brightness);
} // end int Monitor::actionBrightness(int p_brightness)
2019-04-16 00:55:28 +08:00
int Monitor::actionContrast(int p_contrast) {
2016-06-22 02:02:36 +08:00
if ( purpose != CAPTURE ) {
if ( p_contrast >= 0 ) {
2016-04-29 20:57:31 +08:00
shared_data->contrast = p_contrast;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & SET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to set contrast");
return -1;
2016-04-29 20:57:31 +08:00
}
}
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & GET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to get contrast");
return -1;
2016-04-29 20:57:31 +08:00
}
}
}
2019-04-16 00:55:28 +08:00
return shared_data->contrast;
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
return camera->Contrast(p_contrast);
} // end int Monitor::actionContrast(int p_contrast)
2019-04-16 00:55:28 +08:00
int Monitor::actionHue(int p_hue) {
2016-06-22 02:02:36 +08:00
if ( purpose != CAPTURE ) {
if ( p_hue >= 0 ) {
2016-04-29 20:57:31 +08:00
shared_data->hue = p_hue;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & SET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to set hue");
return -1;
2016-04-29 20:57:31 +08:00
}
}
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & GET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to get hue");
return -1;
2016-04-29 20:57:31 +08:00
}
}
}
2019-04-16 00:55:28 +08:00
return shared_data->hue;
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
return camera->Hue(p_hue);
} // end int Monitor::actionHue(int p_hue)
2019-04-16 00:55:28 +08:00
int Monitor::actionColour(int p_colour) {
2016-06-22 02:02:36 +08:00
if ( purpose != CAPTURE ) {
if ( p_colour >= 0 ) {
2016-04-29 20:57:31 +08:00
shared_data->colour = p_colour;
shared_data->action |= SET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & SET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to set colour");
return -1;
2016-04-29 20:57:31 +08:00
}
}
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
shared_data->action |= GET_SETTINGS;
int wait_loops = 10;
2016-06-22 02:02:36 +08:00
while ( shared_data->action & GET_SETTINGS ) {
2016-12-09 00:49:54 +08:00
if ( wait_loops-- ) {
2019-04-16 00:55:28 +08:00
usleep(100000);
2016-12-09 00:49:54 +08:00
} else {
2019-04-16 00:55:28 +08:00
Warning("Timed out waiting to get colour");
return -1;
2016-04-29 20:57:31 +08:00
}
}
}
2019-04-16 00:55:28 +08:00
return shared_data->colour;
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
return camera->Colour(p_colour);
} // end int Monitor::actionColour(int p_colour)
2019-04-16 00:55:28 +08:00
void Monitor::DumpZoneImage(const char *zone_string) {
2016-04-29 20:57:31 +08:00
int exclude_id = 0;
int extra_colour = 0;
Polygon extra_zone;
2016-06-22 02:02:36 +08:00
if ( zone_string ) {
2018-04-12 22:28:22 +08:00
if ( !Zone::ParseZoneString(zone_string, exclude_id, extra_colour, extra_zone) ) {
Error("Failed to parse zone string, ignoring");
2016-04-29 20:57:31 +08:00
}
}
Image *zone_image = nullptr;
2016-04-29 20:57:31 +08:00
if ( ( (!staticConfig.SERVER_ID) || ( staticConfig.SERVER_ID == server_id ) ) && mem_ptr ) {
Debug(3, "Trying to load from local zmc");
int index = shared_data->last_write_index;
2017-11-22 08:55:40 +08:00
ZMPacket *snap = getSnapshot(index);
2018-04-12 22:28:22 +08:00
zone_image = new Image(*snap->image);
2016-04-29 20:57:31 +08:00
} else {
Debug(3, "Trying to load from event");
// Grab the most revent event image
std::string sql = stringtf("SELECT MAX(`Id`) FROM `Events` WHERE `MonitorId`=%d AND `Frames` > 0", id);
2016-04-29 20:57:31 +08:00
zmDbRow eventid_row;
2018-04-12 22:28:22 +08:00
if ( eventid_row.fetch(sql.c_str()) ) {
uint64_t event_id = atoll(eventid_row[0]);
2016-04-29 20:57:31 +08:00
Debug(3, "Got event %" PRIu64, event_id);
2016-04-29 20:57:31 +08:00
EventStream *stream = new EventStream();
2018-04-12 22:28:22 +08:00
stream->setStreamStart(event_id, (unsigned int)1);
2016-04-29 20:57:31 +08:00
zone_image = stream->getImage();
2018-04-12 22:26:18 +08:00
delete stream;
stream = nullptr;
2016-04-29 20:57:31 +08:00
} else {
2018-04-12 22:28:22 +08:00
Error("Unable to load an event for monitor %d", id);
2016-04-29 20:57:31 +08:00
return;
}
}
2018-04-12 22:28:22 +08:00
if ( zone_image->Colours() == ZM_COLOUR_GRAY8 ) {
zone_image->Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
for ( int i = 0; i < n_zones; i++ ) {
2016-04-29 20:57:31 +08:00
if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id )
continue;
Rgb colour;
2016-06-22 02:02:36 +08:00
if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) {
2016-04-29 20:57:31 +08:00
colour = extra_colour;
2016-06-22 02:02:36 +08:00
} else {
if ( zones[i]->IsActive() ) {
colour = kRGBRed;
2016-06-22 02:02:36 +08:00
} else if ( zones[i]->IsInclusive() ) {
colour = kRGBOrange;
2016-06-22 02:02:36 +08:00
} else if ( zones[i]->IsExclusive() ) {
colour = kRGBPurple;
2016-06-22 02:02:36 +08:00
} else if ( zones[i]->IsPreclusive() ) {
colour = kRGBBlue;
2016-06-22 02:02:36 +08:00
} else {
colour = kRGBWhite;
2016-04-29 20:57:31 +08:00
}
}
2019-04-16 00:55:28 +08:00
zone_image->Fill(colour, 2, zones[i]->GetPolygon());
zone_image->Outline(colour, zones[i]->GetPolygon());
2016-04-29 20:57:31 +08:00
}
2016-06-22 02:02:36 +08:00
if ( extra_zone.getNumCoords() ) {
2019-04-16 00:55:28 +08:00
zone_image->Fill(extra_colour, 2, extra_zone);
zone_image->Outline(extra_colour, extra_zone);
2016-04-29 20:57:31 +08:00
}
static char filename[PATH_MAX];
2019-04-16 00:55:28 +08:00
snprintf(filename, sizeof(filename), "Zones%d.jpg", id);
zone_image->WriteJpeg(filename);
2016-04-29 20:57:31 +08:00
delete zone_image;
2019-04-16 00:55:28 +08:00
} // end void Monitor::DumpZoneImage(const char *zone_string)
2019-04-16 00:55:28 +08:00
void Monitor::DumpImage(Image *dump_image) const {
2016-06-22 02:02:36 +08:00
if ( image_count && !(image_count%10) ) {
2016-04-29 20:57:31 +08:00
static char filename[PATH_MAX];
static char new_filename[PATH_MAX];
2019-04-16 00:55:28 +08:00
snprintf(filename, sizeof(filename), "Monitor%d.jpg", id);
snprintf(new_filename, sizeof(new_filename), "Monitor%d-new.jpg", id);
if ( dump_image->WriteJpeg(new_filename) )
rename(new_filename, filename);
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
} // end void Monitor::DumpImage(Image *dump_image)
2019-04-16 00:55:28 +08:00
bool Monitor::CheckSignal(const Image *image) {
2016-04-29 20:57:31 +08:00
static bool static_undef = true;
/* RGB24 colors */
static uint8_t red_val;
static uint8_t green_val;
static uint8_t blue_val;
static uint8_t grayscale_val; /* 8bit grayscale color */
2016-04-29 20:57:31 +08:00
static Rgb colour_val; /* RGB32 color */
static int usedsubpixorder;
if ( signal_check_points > 0 ) {
2016-06-22 02:02:36 +08:00
if ( static_undef ) {
2016-04-29 20:57:31 +08:00
static_undef = false;
usedsubpixorder = camera->SubpixelOrder();
colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */
colour_val = rgb_convert(colour_val, usedsubpixorder);
red_val = RED_VAL_BGRA(signal_check_colour);
green_val = GREEN_VAL_BGRA(signal_check_colour);
blue_val = BLUE_VAL_BGRA(signal_check_colour);
grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */
}
const uint8_t *buffer = image->Buffer();
int pixels = image->Pixels();
int width = image->Width();
int colours = image->Colours();
int index = 0;
for ( int i = 0; i < signal_check_points; i++ ) {
2016-06-22 02:02:36 +08:00
while( true ) {
// Why the casting to long long? also note that on a 64bit cpu, long long is 128bits
2016-04-29 20:57:31 +08:00
index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX);
if ( !config.timestamp_on_capture || !label_format[0] )
break;
// Avoid sampling the rows with timestamp in
if ( index < (label_coord.Y()*width) || index >= (label_coord.Y()+Image::LINE_HEIGHT)*width )
break;
}
2018-03-27 23:04:40 +08:00
if ( colours == ZM_COLOUR_GRAY8 ) {
2016-06-22 02:02:36 +08:00
if ( *(buffer+index) != grayscale_val )
2016-04-29 20:57:31 +08:00
return true;
2016-06-22 02:02:36 +08:00
2018-03-27 23:04:40 +08:00
} else if ( colours == ZM_COLOUR_RGB24 ) {
2016-06-22 02:02:36 +08:00
const uint8_t *ptr = buffer+(index*colours);
2018-03-27 23:04:40 +08:00
if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR ) {
2016-06-22 02:02:36 +08:00
if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) )
return true;
} else {
/* Assume RGB */
if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) )
return true;
}
2018-03-27 23:04:40 +08:00
} else if ( colours == ZM_COLOUR_RGB32 ) {
if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR ) {
2016-06-22 02:02:36 +08:00
if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) )
return true;
} else {
/* Assume RGBA or BGRA */
if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) )
return true;
}
2016-04-29 20:57:31 +08:00
}
2018-03-27 23:04:40 +08:00
} // end for < signal_check_points
2019-04-16 00:55:28 +08:00
Debug(1, "SignalCheck: %d points, colour_val(%d)", signal_check_points, colour_val);
2018-03-27 23:04:40 +08:00
return false;
} // end if signal_check_points
return true;
2019-04-16 00:55:28 +08:00
} // end bool Monitor::CheckSignal(const Image *image)
2017-11-21 00:48:56 +08:00
void Monitor::CheckAction() {
2016-04-29 20:57:31 +08:00
struct timeval now;
gettimeofday(&now, nullptr);
2016-04-29 20:57:31 +08:00
2016-06-22 02:02:36 +08:00
if ( shared_data->action ) {
2016-06-22 00:21:18 +08:00
// Can there be more than 1 bit set in the action? Shouldn't these be elseifs?
2016-06-22 02:02:36 +08:00
if ( shared_data->action & RELOAD ) {
Info("Received reload indication at count %d", image_count);
2016-04-29 20:57:31 +08:00
shared_data->action &= ~RELOAD;
Reload();
}
2016-06-22 02:02:36 +08:00
if ( shared_data->action & SUSPEND ) {
if ( Active() ) {
Info("Received suspend indication at count %d", image_count);
2016-04-29 20:57:31 +08:00
shared_data->active = false;
//closeEvent();
2016-06-22 00:21:18 +08:00
} else {
Info("Received suspend indication at count %d, but wasn't active", image_count);
2016-04-29 20:57:31 +08:00
}
2016-06-22 02:02:36 +08:00
if ( config.max_suspend_time ) {
2016-04-29 20:57:31 +08:00
auto_resume_time = now.tv_sec + config.max_suspend_time;
}
shared_data->action &= ~SUSPEND;
2017-11-21 00:48:56 +08:00
} else if ( shared_data->action & RESUME ) {
2016-06-22 02:02:36 +08:00
if ( Enabled() && !Active() ) {
Info("Received resume indication at count %d", image_count);
2016-04-29 20:57:31 +08:00
shared_data->active = true;
2017-11-21 00:48:56 +08:00
ref_image = *(image_buffer[shared_data->last_write_index].image);
ready_count = std::max(warmup_count, pre_event_count);
2016-04-29 20:57:31 +08:00
shared_data->alarm_x = shared_data->alarm_y = -1;
}
shared_data->action &= ~RESUME;
}
2017-05-20 21:03:51 +08:00
} // end if shared_data->action
2016-06-22 02:02:36 +08:00
if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) {
Info("Auto resuming at count %d", image_count);
2016-04-29 20:57:31 +08:00
shared_data->active = true;
ref_image.Assign(*(image_buffer[shared_data->last_write_index].image));
ready_count = std::max(warmup_count, pre_event_count);
2016-04-29 20:57:31 +08:00
auto_resume_time = 0;
}
2017-11-22 08:55:40 +08:00
}
2017-11-21 00:48:56 +08:00
void Monitor::UpdateCaptureFPS() {
if ( fps_report_interval and
(
!(image_count%fps_report_interval)
or
( (image_count < fps_report_interval) and !(image_count%10) )
)
) {
struct timeval now;
gettimeofday(&now, nullptr);
double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec);
double elapsed = now_double - last_fps_time;
// If we are too fast, we get div by zero. This seems to happen in the case of audio packets.
// Also only do the update at most 1/sec
if ( elapsed > 1.0 ) {
// # of images per interval / the amount of time it took
double new_capture_fps = double((image_count - last_capture_image_count)/elapsed);
unsigned int new_camera_bytes = camera->Bytes();
unsigned int new_capture_bandwidth = (new_camera_bytes-last_camera_bytes)/elapsed;
last_camera_bytes = new_camera_bytes;
Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", "Capturing", image_count,
last_capture_image_count, image_count - last_capture_image_count,
now_double, last_analysis_fps_time,
elapsed, new_capture_fps
);
Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec",
name, image_count, new_capture_fps, new_capture_bandwidth);
shared_data->capture_fps = new_capture_fps;
last_fps_time = now_double;
last_capture_image_count = image_count;
static char sql[ZM_SQL_SML_BUFSIZ];
// The reason we update the Status as well is because if mysql restarts, the Monitor_Status table is lost,
// and nothing else will update the status until zmc restarts. Since we are successfully capturing we can
// assume that we are connected
snprintf(sql, sizeof(sql),
"INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth,Status) "
"VALUES (%d, %.2lf, %u, 'Connected') ON DUPLICATE KEY UPDATE "
"CaptureFPS = %.2lf, CaptureBandwidth=%u, Status='Connected'",
id, new_capture_fps, new_capture_bandwidth, new_capture_fps, new_capture_bandwidth);
dbQueue.push(sql);
} // now != last_fps_time
} // end if report fps
} // void Monitor::UpdateCaptureFPS()
2016-04-29 20:57:31 +08:00
void Monitor::UpdateAnalysisFPS() {
Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d",
analysis_image_count, motion_frame_count, fps_report_interval,
((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) );
if (
( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) )
or
// In startup do faster updates
( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) )
) {
//if ( analysis_image_count && fps_report_interval && !(analysis_image_count%fps_report_interval) ) {
struct timeval now;
gettimeofday(&now, nullptr);
double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec);
double elapsed = now_double - last_analysis_fps_time;
Debug(4, "%s: %d - now:%d.%d = %lf, last %lf, diff %lf", name, analysis_image_count,
now.tv_sec, now.tv_usec, now_double, last_analysis_fps_time,
elapsed
);
if ( elapsed > 1.0 ) {
double new_analysis_fps = double(motion_frame_count - last_motion_frame_count) / elapsed;
Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf",
name, analysis_image_count, new_analysis_fps,
motion_frame_count, last_motion_frame_count, (motion_frame_count - last_motion_frame_count),
now_double, last_analysis_fps_time, elapsed);
if ( new_analysis_fps != shared_data->analysis_fps ) {
shared_data->analysis_fps = new_analysis_fps;
char sql[ZM_SQL_SML_BUFSIZ];
snprintf(sql, sizeof(sql),
"INSERT INTO Monitor_Status (MonitorId,AnalysisFPS) VALUES (%d, %.2lf)"
" ON DUPLICATE KEY UPDATE AnalysisFPS = %.2lf",
id, new_analysis_fps, new_analysis_fps);
dbQueue.push(sql);
last_analysis_fps_time = now_double;
last_motion_frame_count = motion_frame_count;
} else {
Debug(4, "No change in fps");
} // end if change in fps
} // end if at least 1 second has passed since last update
} // end if time to do an update
} // end void Monitor::UpdateAnalysisFPS
2017-11-22 08:55:40 +08:00
// Would be nice if this JUST did analysis
// This idea is that we should be analysing as close to the capture frame as possible.
// This function should process as much as possible before returning
//
// If there is an event, the we should do our best to empty the queue.
// If there isn't then we keep pre-event + alarm frames. = pre_event_count
bool Monitor::Analyse() {
if ( !Enabled() ) {
2020-09-26 04:19:52 +08:00
Warning("Shouldn't be doing Analyse when not Enabled");
2017-11-18 03:30:38 +08:00
return false;
}
if ( !analysis_it )
analysis_it = packetqueue.get_video_it(true);
2016-04-29 20:57:31 +08:00
// if have event, send frames until we find a video packet, at which point do analysis. Adaptive skip should only affect which frames we do analysis on.
2016-04-29 20:57:31 +08:00
// get_analysis_packet will lock the packet and may wait if analysis_it is at the end
ZMPacket *snap = packetqueue.get_packet(analysis_it);
if ( !snap ) return false;
2017-12-01 03:37:36 +08:00
// Is it possible for snap->score to be ! -1 ? Not if everything is working correctly
if ( snap->score != -1 ) {
snap->unlock();
packetqueue.increment_it(analysis_it);
Error("skipping because score was %d", snap->score);
return false;
}
packetqueue_iterator snap_it = *analysis_it;
packetqueue.increment_it(analysis_it);
// signal is set by capture
bool signal = shared_data->signal;
bool signal_change = (signal != last_signal);
Debug(3, "Motion detection is enabled signal(%d) signal_change(%d) trigger state(%d) image index %d",
signal, signal_change, int(trigger_data->trigger_state), snap->image_index);
// Need to guard around event creation/deletion from Reload()
2021-02-12 23:45:32 +08:00
std::lock_guard<std::mutex> lck(event_mutex);
// 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?");
snap->unlock();
return false;
}
2018-04-15 05:03:45 +08:00
Debug(4, "Ready");
std::string cause;
Event::StringSetMap noteSetMap;
2017-11-28 03:57:24 +08:00
// Specifically told to be on. Setting the score here will trigger the alarm.
if ( trigger_data->trigger_state == TriggerState::TRIGGER_ON ) {
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;
} // end if trigger_on
if ( signal_change ) {
2021-02-20 04:56:30 +08:00
Debug(2, "Signal change, new signal is %d", signal);
const char *signalText = "Unknown";
if ( !signal ) {
signalText = "Lost";
if ( event ) {
Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name, analysis_image_count, event->Id());
closeEvent();
last_section_mod = 0;
}
} else {
signalText = "Reacquired";
score += 100;
}
if ( !event ) {
if ( cause.length() )
cause += ", ";
cause += SIGNAL_CAUSE;
}
Event::StringSet noteSet;
noteSet.insert(signalText);
noteSetMap[SIGNAL_CAUSE] = noteSet;
shared_data->state = state = IDLE;
shared_data->active = signal;
if ( (function == MODECT or function == MOCORD) and snap->image )
ref_image.Assign(*(snap->image));
}// else
if ( signal ) {
if ( snap->image or (snap->packet.stream_index == video_stream_id) ) {
struct timeval *timestamp = snap->timestamp;
if ( Active() and (function == MODECT or function == MOCORD) and snap->image ) {
Debug(3, "signal and active and modect");
Event::StringSet zoneSet;
int motion_score = last_motion_score;
if ( analysis_fps_limit ) {
double capture_fps = get_capture_fps();
motion_frame_skip = capture_fps / analysis_fps_limit;
Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)",
motion_frame_skip, capture_fps, analysis_fps_limit);
2016-04-29 20:57:31 +08:00
}
if ( !(analysis_image_count % (motion_frame_skip+1)) ) {
if ( snap->image ) {
// Get new score.
motion_score = DetectMotion(*(snap->image), zoneSet);
Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)",
score, last_motion_score, motion_score);
} else {
Warning("No image in snap");
2016-04-29 20:57:31 +08:00
}
// Why are we updating the last_motion_score too?
last_motion_score = motion_score;
motion_frame_count += 1;
} else {
Debug(1, "Skipped motion detection");
2016-04-29 20:57:31 +08:00
}
if ( motion_score ) {
score += motion_score;
2016-04-29 20:57:31 +08:00
if ( cause.length() )
cause += ", ";
cause += MOTION_CAUSE;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
} // end if active and doing motion detection
// Check to see if linked monitors are triggering.
if ( n_linked_monitors > 0 ) {
Debug(4, "Checking linked monitors");
// FIXME improve logic here
bool first_link = true;
2016-04-29 20:57:31 +08:00
Event::StringSet noteSet;
for ( int i = 0; i < n_linked_monitors; i++ ) {
// TODO: Shouldn't we try to connect?
if ( linked_monitors[i]->isConnected() ) {
Debug(4, "Linked monitor %d %s is connected",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
if ( linked_monitors[i]->hasAlarmed() ) {
Debug(4, "Linked monitor %d %s is alarmed",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
if ( !event ) {
if ( first_link ) {
if ( cause.length() )
cause += ", ";
cause += LINKED_CAUSE;
first_link = false;
}
}
noteSet.insert(linked_monitors[i]->Name());
score += linked_monitors[i]->lastFrameScore(); // 50;
} else {
Debug(4, "Linked monitor %d %s is not alarmed",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
}
} else {
Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id());
linked_monitors[i]->connect();
}
} // end foreach linked_monitor
if ( noteSet.size() > 0 )
noteSetMap[LINKED_CAUSE] = noteSet;
} // end if linked_monitors
if ( function == RECORD || function == MOCORD ) {
// If doing record, check to see if we need to close the event or not.
if ( event ) {
Debug(2, "Have event %" PRIu64 " in mocord", event->Id());
if ( section_length
&& ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length )
&& ( (function == MOCORD && (event_close_mode != CLOSE_TIME)) || ! ( timestamp->tv_sec % section_length ) )
) {
Info("%s: %03d - Closing event %" PRIu64 ", section end forced %d - %d = %d >= %d",
name, image_count, event->Id(),
timestamp->tv_sec, video_store_data->recording.tv_sec,
timestamp->tv_sec - video_store_data->recording.tv_sec,
section_length
);
closeEvent();
} // end if section_length
} // end if event
if ( !event ) {
Debug(2, "Creating continuous event");
if ( !snap->keyframe and (videowriter == PASSTHROUGH) ) {
// Must start on a keyframe so rewind. Only for passthrough though I guess.
// FIXME this iterator is not protected from invalidation
packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it(
snap_it, 0 /* pre_event_count */
);
// This gets a lock on the starting packet
ZMPacket *starting_packet = packetqueue.get_packet(start_it);
event = new Event(this, *(starting_packet->timestamp), "Continuous", noteSetMap);
// Write out starting packets, do not modify packetqueue it will garbage collect itself
while ( starting_packet and (*start_it) != snap_it ) {
event->AddPacket(starting_packet);
// Have added the packet, don't want to unlock it until we have locked the next
packetqueue.increment_it(start_it);
if ( (*start_it) == snap_it ) {
starting_packet->unlock();
break;
}
ZMPacket *p = packetqueue.get_packet(start_it);
starting_packet->unlock();
starting_packet = p;
}
packetqueue.free_it(start_it);
delete start_it;
start_it = nullptr;
} else {
// Create event from current snap
event = new Event(this, *timestamp, "Continuous", noteSetMap);
}
shared_data->last_event_id = event->Id();
// lets construct alarm cause. It will contain cause + names of zones alarmed
std::string alarm_cause = "Continuous";
for ( int i=0; i < n_zones; i++ ) {
if ( zones[i]->Alarmed() ) {
alarm_cause += std::string(zones[i]->Label());
if ( i < n_zones-1 ) {
alarm_cause += ",";
}
}
}
alarm_cause = cause+" "+alarm_cause;
strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1);
video_store_data->recording = event->StartTime();
Info("%s: %03d - Opened new event %" PRIu64 ", section start",
name, analysis_image_count, event->Id());
/* To prevent cancelling out an existing alert\prealarm\alarm state */
if ( state == IDLE ) {
shared_data->state = state = TAPE;
}
} // end if ! event
} // end if RECORDING
if ( score ) {
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->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length )
&& ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) )
) {
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
name, image_count, event->Id());
closeEvent();
} else if ( event ) {
// This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames
Debug(3, "pre-alarm-count in event %d, event frames %d, alarm frames %d event length %d >=? %d min",
Event::PreAlarmCount(), event->Frames(), event->AlarmFrames(),
( timestamp->tv_sec - video_store_data->recording.tv_sec ), min_section_length
);
}
if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) {
// lets construct alarm cause. It will contain cause + names of zones alarmed
std::string alarm_cause = "";
for ( int i=0; i < n_zones; i++ ) {
if ( zones[i]->Alarmed() ) {
alarm_cause = alarm_cause + "," + std::string(zones[i]->Label());
}
}
if ( !alarm_cause.empty() ) alarm_cause[0] = ' ';
alarm_cause = cause + alarm_cause;
strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1);
Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s",
name, image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause);
if ( !event ) {
packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it(
snap_it,
(pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)
);
ZMPacket *starting_packet = *(*start_it);
event = new Event(this, *(starting_packet->timestamp), cause, noteSetMap);
// Write out starting packets, do not modify packetqueue it will garbage collect itself
while ( *start_it != snap_it ) {
event->AddPacket(starting_packet);
packetqueue.increment_it(start_it);
if ( (*start_it) == snap_it ) {
starting_packet->unlock();
break;
2016-04-29 20:57:31 +08:00
}
ZMPacket *p = packetqueue.get_packet(start_it);
starting_packet->unlock();
starting_packet = p;
}
packetqueue.free_it(start_it);
delete start_it;
start_it = nullptr;
2017-11-22 08:55:40 +08:00
shared_data->last_event_id = event->Id();
2019-05-06 22:50:12 +08:00
//set up video store data
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
video_store_data->recording = event->StartTime();
shared_data->state = state = ALARM;
Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name, analysis_image_count, event->Id());
} // end if no event, so start it
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, analysis_image_count);
shared_data->state = state = PREALARM;
2016-04-29 20:57:31 +08:00
}
} else if ( state == ALERT ) {
alert_to_alarm_frame_count--;
Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d",
name, analysis_image_count, alert_to_alarm_frame_count);
if (alert_to_alarm_frame_count == 0) {
Info("%s: %03d - Gone back into alarm state", name, analysis_image_count);
shared_data->state = state = ALARM;
}
}
last_alarm_count = analysis_image_count;
} else { // no 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, analysis_image_count);
shared_data->state = state = ALERT;
} else if ( state == ALERT ) {
if (
( analysis_image_count-last_alarm_count > post_event_count )
&&
( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length )
) {
Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images",
name, analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
//if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE )
if ( (function != RECORD && function != MOCORD ) || event_close_mode == CLOSE_ALARM ) {
shared_data->state = state = IDLE;
Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s",
name, analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" );
closeEvent();
} else {
shared_data->state = state = TAPE;
}
}
} else if ( state == PREALARM ) {
// Back to IDLE
shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE);
} else {
Debug(1, "State %d because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%d) - recording.tv_src(%d) >= min_section_length(%d)",
state, analysis_image_count, last_alarm_count, post_event_count,
timestamp->tv_sec, video_store_data->recording.tv_sec, min_section_length);
}
if ( Event::PreAlarmCount() )
Event::EmptyPreAlarmFrames();
} // end if score or not
snap->score = score;
if ( state == PREALARM ) {
// Generate analysis images if necessary
if ( (savejpegs > 1) and snap->image ) {
for ( int i = 0; i < n_zones; i++ ) {
if ( zones[i]->Alarmed() ) {
if ( zones[i]->AlarmImage() ) {
if ( ! snap->analysis_image )
snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zones[i]->AlarmImage()));
}
} // end if zone is alarmed
} // end foreach zone
} // end if savejpegs
// incremement pre alarm image count
//have_pre_alarmed_frames ++;
Event::AddPreAlarmFrame(snap->image, *timestamp, score, nullptr);
} else if ( state == ALARM ) {
if ( ( savejpegs > 1 ) and snap->image ) {
for ( int i = 0; i < n_zones; i++ ) {
if ( zones[i]->Alarmed() ) {
if ( zones[i]->AlarmImage() ) {
if ( ! snap->analysis_image )
snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zones[i]->AlarmImage()));
}
if ( config.record_event_stats )
zones[i]->RecordStats(event);
} // end if zone is alarmed
} // end foreach zone
}
if ( noteSetMap.size() > 0 )
event->updateNotes(noteSetMap);
if ( section_length
&& ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length )
&& ! (image_count % fps_report_interval)
) {
Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %d - %d = %d >= %d",
name, image_count, event->Id(),
timestamp->tv_sec, video_store_data->recording.tv_sec,
timestamp->tv_sec - video_store_data->recording.tv_sec,
section_length
);
closeEvent();
event = new Event(this, *timestamp, cause, noteSetMap);
shared_data->last_event_id = event->Id();
//set up video store data
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
video_store_data->recording = event->StartTime();
}
} else if ( state == ALERT ) {
// Alert means this frame has no motion, but we were alarmed and are still recording.
if ( noteSetMap.size() > 0 )
event->updateNotes(noteSetMap);
} else if ( state == TAPE ) {
// bulk frame code moved to event.
} // end if state machine
if ( (function == MODECT or function == MOCORD) and snap->image ) {
ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc ));
}
last_signal = signal;
} // end if videostream
} // end if signal
shared_data->last_frame_score = score;
} else {
Debug(3, "trigger == off");
if ( event ) {
Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name, analysis_image_count, event->Id());
closeEvent();
}
shared_data->state = state = IDLE;
trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL;
} // end if ( trigger_data->trigger_state != TRIGGER_OFF )
2017-04-06 04:10:21 +08:00
if (event) event->AddPacket(snap);
#if 0
if (snap->packet.stream_index == video_stream_id) {
if (video_fifo) {
if ( snap->keyframe ) {
// avcodec strips out important nals that describe the stream and
// stick them in extradata. Need to send them along with keyframes
AVStream *stream = camera->get_VideoStream();
video_fifo->write(
static_cast<unsigned char *>(stream->codecpar->extradata),
stream->codecpar->extradata_size);
}
video_fifo->writePacket(*snap);
}
} else if (snap->packet.stream_index == audio_stream_id) {
if (audio_fifo)
audio_fifo->writePacket(*snap);
}
#endif
2016-04-29 20:57:31 +08:00
if ((videowriter == PASSTHROUGH) and !savejpegs) {
// Don't need raw images anymore
delete snap->image;
snap->image = nullptr;
}
// popPacket will have placed a second lock on snap, so release it here.
snap->unlock();
if ( snap->image_index > 0 ) {
// Only do these if it's a video packet.
shared_data->last_read_index = snap->image_index;
analysis_image_count++;
if ( function == MODECT or function == MOCORD )
UpdateAnalysisFPS();
}
shared_data->last_read_time = time(nullptr);
packetqueue.clearPackets(snap);
return true;
} // end Monitor::Analyse
2016-06-22 02:02:36 +08:00
void Monitor::Reload() {
2019-04-16 00:55:28 +08:00
Debug(1, "Reloading monitor %s", name);
2016-04-29 20:57:31 +08:00
// Access to the event needs to be protected. Either thread could call Reload. Either thread could close the event.
// Need a mutex on it I guess. FIXME
// Need to guard around event creation/deletion This will prevent event creation until new settings are loaded
2021-02-12 23:45:32 +08:00
std::lock_guard<std::mutex> lck(event_mutex);
if ( event ) {
2019-04-16 00:55:28 +08:00
Info("%s: %03d - Closing event %" PRIu64 ", reloading", name, image_count, event->Id());
closeEvent();
}
2016-04-29 20:57:31 +08:00
std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", id);
zmDbRow *row = zmDbFetchOne(sql.c_str());
if ( !row ) {
Error("Can't run query: %s", mysql_error(&dbconn));
} else if ( MYSQL_ROW dbrow = row->mysql_row() ) {
Load(dbrow, 1, purpose);
2016-04-29 20:57:31 +08:00
shared_data->state = state = IDLE;
shared_data->alarm_x = shared_data->alarm_y = -1;
if ( enabled )
shared_data->active = true;
ready_count = std::max(warmup_count, pre_event_count);
2016-04-29 20:57:31 +08:00
2018-04-12 23:29:35 +08:00
delete row;
} // end if row
2016-04-29 20:57:31 +08:00
ReloadZones();
2020-04-17 05:54:20 +08:00
} // end void Monitor::Reload()
2016-06-22 02:02:36 +08:00
void Monitor::ReloadZones() {
2019-04-16 00:55:28 +08:00
Debug(1, "Reloading zones for monitor %s", name);
2016-06-22 02:02:36 +08:00
for( int i = 0; i < n_zones; i++ ) {
2016-04-29 20:57:31 +08:00
delete zones[i];
}
delete[] zones;
zones = nullptr;
2019-04-16 00:55:28 +08:00
n_zones = Zone::Load(this, zones);
2016-04-29 20:57:31 +08:00
//DumpZoneImage();
2019-04-16 00:55:28 +08:00
} // end void Monitor::ReloadZones()
void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) {
Debug(1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors);
2016-06-22 02:02:36 +08:00
if ( n_linked_monitors ) {
2020-12-15 23:14:19 +08:00
for ( int i=0; i < n_linked_monitors; i++ ) {
2016-04-29 20:57:31 +08:00
delete linked_monitors[i];
}
delete[] linked_monitors;
linked_monitors = nullptr;
2016-04-29 20:57:31 +08:00
}
n_linked_monitors = 0;
2016-06-22 02:02:36 +08:00
if ( p_linked_monitors ) {
2016-04-29 20:57:31 +08:00
int n_link_ids = 0;
unsigned int link_ids[256];
// This nasty code picks out strings of digits from p_linked_monitors and tries to load them.
2016-04-29 20:57:31 +08:00
char link_id_str[8];
char *dest_ptr = link_id_str;
const char *src_ptr = p_linked_monitors;
2019-04-16 00:55:28 +08:00
while ( 1 ) {
2016-04-29 20:57:31 +08:00
dest_ptr = link_id_str;
2019-04-16 00:55:28 +08:00
while ( *src_ptr >= '0' && *src_ptr <= '9' ) {
2016-06-22 02:02:36 +08:00
if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) {
2016-04-29 20:57:31 +08:00
*dest_ptr++ = *src_ptr++;
2016-06-22 02:02:36 +08:00
} else {
2016-04-29 20:57:31 +08:00
break;
}
}
// Add the link monitor
2016-06-22 02:02:36 +08:00
if ( dest_ptr != link_id_str ) {
2016-04-29 20:57:31 +08:00
*dest_ptr = '\0';
unsigned int link_id = atoi(link_id_str);
if ( link_id > 0 && link_id != id ) {
Debug(3, "Found linked monitor id %d", link_id);
2016-04-29 20:57:31 +08:00
int j;
2016-06-22 02:02:36 +08:00
for ( j = 0; j < n_link_ids; j++ ) {
2016-04-29 20:57:31 +08:00
if ( link_ids[j] == link_id )
break;
}
if ( j == n_link_ids ) {
// Not already found
2016-04-29 20:57:31 +08:00
link_ids[n_link_ids++] = link_id;
}
}
}
if ( !*src_ptr )
break;
while( *src_ptr && (*src_ptr < '0' || *src_ptr > '9') )
src_ptr++;
if ( !*src_ptr )
break;
}
2016-06-22 02:02:36 +08:00
if ( n_link_ids > 0 ) {
Debug(1, "Linking to %d monitors", n_link_ids);
2016-04-29 20:57:31 +08:00
linked_monitors = new MonitorLink *[n_link_ids];
int count = 0;
2016-06-22 02:02:36 +08:00
for ( int i = 0; i < n_link_ids; i++ ) {
Debug(1, "Checking linked monitor %d", link_ids[i]);
2016-04-29 20:57:31 +08:00
std::lock_guard<std::mutex> lck(db_mutex);
2016-04-29 20:57:31 +08:00
static char sql[ZM_SQL_SML_BUFSIZ];
2019-08-18 02:36:52 +08:00
snprintf(sql, sizeof(sql),
"SELECT `Id`, `Name` FROM `Monitors`"
" WHERE `Id` = %d"
" AND `Function` != 'None'"
" AND `Function` != 'Monitor'"
" AND `Enabled`=1",
link_ids[i]);
if (mysql_query(&dbconn, sql)) {
Error("Can't run query: %s", mysql_error(&dbconn));
continue;
2016-04-29 20:57:31 +08:00
}
MYSQL_RES *result = mysql_store_result(&dbconn);
2016-06-22 02:02:36 +08:00
if ( !result ) {
Error("Can't use query result: %s", mysql_error(&dbconn));
continue;
2016-04-29 20:57:31 +08:00
}
int n_monitors = mysql_num_rows(result);
2016-06-22 02:02:36 +08:00
if ( n_monitors == 1 ) {
MYSQL_ROW dbrow = mysql_fetch_row(result);
Debug(1, "Linking to monitor %d %s", atoi(dbrow[0]), dbrow[1]);
linked_monitors[count++] = new MonitorLink(link_ids[i], dbrow[1]);
2016-06-22 02:02:36 +08:00
} else {
Warning("Can't link to monitor %d, invalid id, function or not enabled", link_ids[i]);
2016-04-29 20:57:31 +08:00
}
mysql_free_result(result);
} // end foreach link_id
2016-04-29 20:57:31 +08:00
n_linked_monitors = count;
} // end if has link_ids
} // end if p_linked_monitors
} // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors)
std::vector<std::shared_ptr<Monitor>> Monitor::LoadMonitors(std::string where, Purpose purpose) {
std::string sql = load_monitor_sql + " WHERE " + where;
Debug(1, "Loading Monitors with %s", sql.c_str());
2016-04-29 20:57:31 +08:00
MYSQL_RES *result = zmDbFetch(sql.c_str());
if (!result) {
Error("Can't load local monitors: %s", mysql_error(&dbconn));
return {};
2016-04-29 20:57:31 +08:00
}
int n_monitors = mysql_num_rows(result);
2019-04-16 00:55:28 +08:00
Debug(1, "Got %d monitors", n_monitors);
std::vector<std::shared_ptr<Monitor>> monitors;
monitors.reserve(n_monitors);
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
monitors.emplace_back(std::make_shared<Monitor>());
monitors.back()->Load(dbrow, true, purpose);
}
if (mysql_errno(&dbconn)) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
mysql_free_result(result);
return {};
}
mysql_free_result(result);
2016-04-29 20:57:31 +08:00
return monitors;
}
2016-04-29 20:57:31 +08:00
#if ZM_HAS_V4L
std::vector<std::shared_ptr<Monitor>> Monitor::LoadLocalMonitors
(const char *device, Purpose purpose) {
2016-04-29 20:57:31 +08:00
std::string where = "`Function` != 'None' AND `Type` = 'Local'";
2016-04-29 20:57:31 +08:00
if ( device[0] )
where += " AND `Device`='" + std::string(device) + "'";
if (staticConfig.SERVER_ID)
where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID);
return LoadMonitors(where, purpose);
}
#endif // ZM_HAS_V4L
std::vector<std::shared_ptr<Monitor>> Monitor::LoadRemoteMonitors
(const char *protocol, const char *host, const char *port, const char *path, Purpose purpose) {
std::string where = "`Function` != 'None' AND `Type` = 'Remote'";
if (staticConfig.SERVER_ID)
where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID);
if (protocol)
where += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path);
return LoadMonitors(where, purpose);
}
std::vector<std::shared_ptr<Monitor>> Monitor::LoadFileMonitors(const char *file, Purpose purpose) {
std::string where = "`Function` != 'None' AND `Type` = 'File'";
if (file[0])
where += " AND `Path`='" + std::string(file) + "'";
if (staticConfig.SERVER_ID)
where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID);
return LoadMonitors(where, purpose);
}
#if HAVE_LIBAVFORMAT
std::vector<std::shared_ptr<Monitor>> Monitor::LoadFfmpegMonitors(const char *file, Purpose purpose) {
std::string where = "`Function` != 'None' AND `Type` = 'Ffmpeg'";
if (file[0])
where += " AND `Path` = '" + std::string(file) + "'";
if (staticConfig.SERVER_ID)
where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID);
return LoadMonitors(where, purpose);
}
#endif // HAVE_LIBAVFORMAT
/* Returns 0 on success, even if no new images are available (transient error)
* Returns -1 on failure.
*/
int Monitor::Capture() {
static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image
2016-04-29 20:57:31 +08:00
unsigned int index = image_count % image_buffer_count;
2016-04-29 20:57:31 +08:00
ZMPacket *packet = new ZMPacket();
packet->timestamp = new struct timeval;
packet->image_index = image_count;
gettimeofday(packet->timestamp, nullptr);
Image* capture_image = image_buffer[index].image;
2017-10-23 21:51:41 +08:00
int captureResult = 0;
if ( deinterlacing_value == 4 ) {
2016-04-29 20:57:31 +08:00
if ( FirstCapture != 1 ) {
/* Copy the next image into the shared memory */
//capture_image->CopyBuffer(*(next_buffer.image));
2016-04-29 20:57:31 +08:00
}
/* Capture a new next image */
2017-11-14 01:14:57 +08:00
captureResult = camera->Capture(*packet);
// How about set shared_data->current_timestamp
gettimeofday(packet->timestamp, nullptr);
2016-04-29 20:57:31 +08:00
if ( FirstCapture ) {
FirstCapture = 0;
return 0;
}
2016-04-29 20:57:31 +08:00
} else {
2017-11-14 01:14:57 +08:00
captureResult = camera->Capture(*packet);
Debug(4, "Back from capture result=%d image %d", captureResult, image_count);
if ( captureResult < 0 ) {
Debug(2, "failed capture");
2021-01-12 02:35:48 +08:00
// Unable to capture image
2017-10-23 21:51:41 +08:00
// Fake a signal loss image
2021-01-12 02:35:48 +08:00
// Not sure what to do here. We will close monitor and kill analysis_thread but what about rtsp server?
2017-10-23 21:51:41 +08:00
Rgb signalcolor;
/* HTML colour code is actually BGR in memory, we want RGB */
signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR);
2021-01-12 02:35:48 +08:00
capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
2017-10-23 21:51:41 +08:00
capture_image->Fill(signalcolor);
shared_data->signal = false;
shared_data->last_write_index = index;
shared_data->last_write_time = image_buffer[index].timestamp->tv_sec;
2021-01-12 02:35:48 +08:00
image_buffer[index].image->Assign(*capture_image);
*(image_buffer[index].timestamp) = *(packet->timestamp);
delete capture_image;
image_count++;
delete packet;
// What about timestamping it?
// Don't want to do analysis on it, but we won't due to signal
2018-02-03 05:07:13 +08:00
return -1;
} else if ( captureResult > 0 ) {
2020-12-16 04:59:46 +08:00
Debug(2, "Have packet stream_index:%d ?= videostream_id:(%d) q.vpktcount(%d) event?(%d) ",
packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ) );
2017-10-23 21:51:41 +08:00
if (packet->packet.stream_index == video_stream_id) {
if (video_fifo) {
if ( packet->keyframe ) {
// avcodec strips out important nals that describe the stream and
// stick them in extradata. Need to send them along with keyframes
AVStream *stream = camera->get_VideoStream();
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
video_fifo->write(
static_cast<unsigned char *>(stream->codecpar->extradata),
stream->codecpar->extradata_size,
packet->pts);
#endif
}
video_fifo->writePacket(*packet);
}
} else if (packet->packet.stream_index == audio_stream_id) {
if (audio_fifo)
audio_fifo->writePacket(*packet);
}
if ( (packet->packet.stream_index != video_stream_id) and ! packet->image ) {
2019-02-25 23:21:43 +08:00
// Only queue if we have some video packets in there. Should push this logic into packetqueue
if ( record_audio and (packetqueue.packet_count(video_stream_id) or event) ) {
packet->image_index=-1;
2020-09-26 04:19:52 +08:00
Debug(2, "Queueing audio packet");
packetqueue.queuePacket(packet);
} else {
2021-02-20 04:56:30 +08:00
Debug(4, "Not Queueing audio packet");
delete packet;
2017-11-22 08:55:40 +08:00
}
2017-11-22 00:58:15 +08:00
// Don't update last_write_index because that is used for live streaming
//shared_data->last_write_time = image_buffer[index].timestamp->tv_sec;
2017-11-22 00:58:15 +08:00
return 1;
} // end if audio
2016-04-29 20:57:31 +08:00
if ( !packet->image ) {
if ( packet->packet.size and !packet->in_frame ) {
if ( !decoding_enabled ) {
Debug(1, "Not decoding");
} else {
Debug(2,"About to decode %p", packet);
2021-01-12 02:35:48 +08:00
// Allocate the image first so that it can be used by hwaccel
// We don't actually care about camera colours, pixel order etc. We care about the desired settings
//
//capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
2021-01-12 02:35:48 +08:00
int ret = packet->decode(camera->get_VideoCodecContext());
if ( ret < 0 ) {
Error("decode failed");
2021-01-12 02:35:48 +08:00
} else if ( ret == 0 ) {
delete packet;
return 0;
} // end if decode
} // end if decoding
} else {
Debug(1, "No packet.size(%d) or packet->in_frame(%p). Not decoding", packet->packet.size, packet->in_frame);
}
2021-01-12 02:35:48 +08:00
if ( packet->in_frame and !packet->image ) {
capture_image = packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
packet->get_image();
}
} // end if need to decode
if ( packet->image ) {
capture_image = packet->image;
/* Deinterlacing */
if ( deinterlacing_value ) {
if ( deinterlacing_value == 1 ) {
capture_image->Deinterlace_Discard();
} else if ( deinterlacing_value == 2 ) {
capture_image->Deinterlace_Linear();
} else if ( deinterlacing_value == 3 ) {
capture_image->Deinterlace_Blend();
} else if ( deinterlacing_value == 4 ) {
capture_image->Deinterlace_4Field(next_buffer.image, (deinterlacing>>8)&0xff);
} else if ( deinterlacing_value == 5 ) {
capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff);
}
}
2016-04-29 20:57:31 +08:00
if ( orientation != ROTATE_0 ) {
Debug(2, "Doing rotation");
switch ( orientation ) {
case ROTATE_0 :
// No action required
break;
case ROTATE_90 :
case ROTATE_180 :
case ROTATE_270 :
capture_image->Rotate((orientation-1)*90);
break;
case FLIP_HORI :
case FLIP_VERT :
capture_image->Flip(orientation==FLIP_HORI);
break;
}
} // end if have rotation
2016-04-29 20:57:31 +08:00
if ( privacy_bitmask ) {
Debug(1, "Applying privacy");
capture_image->MaskPrivacy(privacy_bitmask);
2016-04-29 20:57:31 +08:00
}
if ( config.timestamp_on_capture ) {
Debug(1, "Timestampprivacy");
TimestampImage(packet->image, packet->timestamp);
}
2017-10-23 21:51:41 +08:00
if ( !ref_image.Buffer() ) {
// First image, so assign it to ref image
Debug(1, "Assigning ref image %dx%d size: %d", width, height, camera->ImageSize());
ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(),
packet->image->Buffer(), camera->ImageSize());
}
image_buffer[index].image->Assign(*(packet->image));
*(image_buffer[index].timestamp) = *(packet->timestamp);
} // end if have image
2017-10-23 21:51:41 +08:00
shared_data->signal = ( capture_image and signal_check_points ) ? CheckSignal(capture_image) : true;
2017-11-18 05:49:01 +08:00
shared_data->last_write_index = index;
shared_data->last_write_time = packet->timestamp->tv_sec;
2017-11-18 05:49:01 +08:00
image_count++;
// Will only be queued if there are iterators allocated in the queue.
if ( !packetqueue.queuePacket(packet) ) {
delete packet;
}
UpdateCaptureFPS();
} else { // result == 0
2017-11-21 00:48:56 +08:00
// Question is, do we update last_write_index etc?
delete packet;
return 0;
} // end if result
2017-11-13 23:17:46 +08:00
} // end if deinterlacing
2016-04-29 20:57:31 +08:00
// Icon: I'm not sure these should be here. They have nothing to do with capturing
if ( shared_data->action & GET_SETTINGS ) {
shared_data->brightness = camera->Brightness();
shared_data->hue = camera->Hue();
shared_data->colour = camera->Colour();
shared_data->contrast = camera->Contrast();
shared_data->action &= ~GET_SETTINGS;
}
if ( shared_data->action & SET_SETTINGS ) {
2019-04-16 00:55:28 +08:00
camera->Brightness(shared_data->brightness);
camera->Hue(shared_data->hue);
camera->Colour(shared_data->colour);
camera->Contrast(shared_data->contrast);
shared_data->action &= ~SET_SETTINGS;
}
return captureResult;
2017-11-10 03:50:20 +08:00
} // end Monitor::Capture
2019-04-16 00:55:28 +08:00
void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) const {
if ( !label_format[0] )
return;
// Expand the strftime macros first
char label_time_text[256];
strftime(label_time_text, sizeof(label_time_text), label_format, localtime(&ts_time->tv_sec));
char label_text[1024];
const char *s_ptr = label_time_text;
char *d_ptr = label_text;
while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) {
if ( *s_ptr == config.timestamp_code_char[0] ) {
bool found_macro = false;
switch ( *(s_ptr+1) ) {
case 'N' :
d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name);
found_macro = true;
break;
case 'Q' :
d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", trigger_data->trigger_showtext);
found_macro = true;
break;
case 'f' :
d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%02ld", ts_time->tv_usec/10000);
found_macro = true;
break;
}
if ( found_macro ) {
s_ptr += 2;
continue;
2016-04-29 20:57:31 +08:00
}
}
2019-04-16 00:55:28 +08:00
*d_ptr++ = *s_ptr++;
} // end while
*d_ptr = '\0';
Debug(2, "annotating %s", label_text);
2019-04-26 02:49:16 +08:00
ts_image->Annotate(label_text, label_coord, label_size);
Debug(2, "done annotating %s", label_text);
2019-04-16 00:55:28 +08:00
} // end void Monitor::TimestampImage
bool Monitor::closeEvent() {
2019-04-16 00:55:28 +08:00
if ( !event )
return false;
delete event;
event = nullptr;
if ( shared_data )
video_store_data->recording = {};
2019-04-16 00:55:28 +08:00
return true;
} // end bool Monitor::closeEvent()
2019-04-16 00:55:28 +08:00
unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zoneSet) {
2016-04-29 20:57:31 +08:00
bool alarm = false;
unsigned int score = 0;
2019-04-16 00:55:28 +08:00
if ( n_zones <= 0 ) return alarm;
2016-04-29 20:57:31 +08:00
2019-04-16 00:55:28 +08:00
ref_image.Delta(comp_image, &delta_image);
2016-04-29 20:57:31 +08:00
2016-12-09 00:49:54 +08:00
if ( config.record_diag_images ) {
ref_image.WriteJpeg(diag_path_ref.c_str(), config.record_diag_images_fifo);
delta_image.WriteJpeg(diag_path_delta.c_str(), config.record_diag_images_fifo);
2016-04-29 20:57:31 +08:00
}
// Blank out all exclusion zones
2016-12-09 00:49:54 +08:00
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
2016-04-29 20:57:31 +08:00
Zone *zone = zones[n_zone];
// need previous alarmed state for preclusive zone, so don't clear just yet
if ( !zone->IsPreclusive() )
2016-04-29 20:57:31 +08:00
zone->ClearAlarm();
2016-12-09 00:49:54 +08:00
if ( !zone->IsInactive() ) {
2016-04-29 20:57:31 +08:00
continue;
}
Debug(3, "Blanking inactive zone %s", zone->Label());
delta_image.Fill(kRGBBlack, zone->GetPolygon());
2019-04-16 00:55:28 +08:00
} // end foreach zone
2016-04-29 20:57:31 +08:00
// Check preclusive zones first
2016-12-09 00:49:54 +08:00
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
2016-04-29 20:57:31 +08:00
Zone *zone = zones[n_zone];
2016-12-09 00:49:54 +08:00
if ( !zone->IsPreclusive() ) {
2016-04-29 20:57:31 +08:00
continue;
}
int old_zone_score = zone->Score();
bool old_zone_alarmed = zone->Alarmed();
2019-04-16 00:55:28 +08:00
Debug(3, "Checking preclusive zone %s - old score: %d, state: %s",
zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet");
if ( zone->CheckAlarms(&delta_image) ) {
2016-04-29 20:57:31 +08:00
alarm = true;
score += zone->Score();
zone->SetAlarm();
2019-04-16 00:55:28 +08:00
Debug(3, "Zone is alarmed, zone score = %d", zone->Score());
zoneSet.insert(zone->Label());
2016-04-29 20:57:31 +08:00
//zone->ResetStats();
} else {
// check if end of alarm
2019-04-16 00:55:28 +08:00
if ( old_zone_alarmed ) {
Debug(3, "Preclusive Zone %s alarm Ends. Previous score: %d",
zone->Label(), old_zone_score);
if ( old_zone_score > 0 ) {
2016-04-29 20:57:31 +08:00
zone->SetExtendAlarmCount(zone->GetExtendAlarmFrames());
}
2019-04-16 00:55:28 +08:00
if ( zone->CheckExtendAlarmCount() ) {
alarm = true;
zone->SetAlarm();
2016-04-29 20:57:31 +08:00
} else {
zone->ClearAlarm();
}
}
2019-04-16 00:55:28 +08:00
} // end if CheckAlarms
} // end foreach zone
2016-04-29 20:57:31 +08:00
Coord alarm_centre;
int top_score = -1;
if ( alarm ) {
2016-04-29 20:57:31 +08:00
alarm = false;
score = 0;
2016-12-09 00:49:54 +08:00
} else {
2016-04-29 20:57:31 +08:00
// Find all alarm pixels in active zones
2016-12-09 00:49:54 +08:00
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
2016-04-29 20:57:31 +08:00
Zone *zone = zones[n_zone];
2016-12-09 00:49:54 +08:00
if ( !zone->IsActive() || zone->IsPreclusive()) {
2016-04-29 20:57:31 +08:00
continue;
}
2019-04-16 00:55:28 +08:00
Debug(3, "Checking active zone %s", zone->Label());
if ( zone->CheckAlarms(&delta_image) ) {
2016-04-29 20:57:31 +08:00
alarm = true;
score += zone->Score();
zone->SetAlarm();
2019-04-16 00:55:28 +08:00
Debug(3, "Zone is alarmed, zone score = %d", zone->Score());
zoneSet.insert(zone->Label());
2016-12-09 00:49:54 +08:00
if ( config.opt_control && track_motion ) {
if ( (int)zone->Score() > top_score ) {
2016-04-29 20:57:31 +08:00
top_score = zone->Score();
alarm_centre = zone->GetAlarmCentre();
}
}
}
2019-04-16 00:55:28 +08:00
} // end foreach zone
2016-04-29 20:57:31 +08:00
2016-12-09 00:49:54 +08:00
if ( alarm ) {
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
2016-04-29 20:57:31 +08:00
Zone *zone = zones[n_zone];
2017-02-19 04:22:56 +08:00
// Wasn't this zone already checked above?
2016-12-09 00:49:54 +08:00
if ( !zone->IsInclusive() ) {
2016-04-29 20:57:31 +08:00
continue;
}
2019-04-16 00:55:28 +08:00
Debug(3, "Checking inclusive zone %s", zone->Label());
if ( zone->CheckAlarms(&delta_image) ) {
2016-04-29 20:57:31 +08:00
alarm = true;
score += zone->Score();
zone->SetAlarm();
2019-04-16 00:55:28 +08:00
Debug(3, "Zone is alarmed, zone score = %d", zone->Score());
2016-04-29 20:57:31 +08:00
zoneSet.insert( zone->Label() );
2016-12-09 00:49:54 +08:00
if ( config.opt_control && track_motion ) {
if ( zone->Score() > (unsigned int)top_score ) {
2016-04-29 20:57:31 +08:00
top_score = zone->Score();
alarm_centre = zone->GetAlarmCentre();
}
}
2019-04-16 00:55:28 +08:00
} // end if CheckAlarm
} // end foreach zone
2016-12-09 00:49:54 +08:00
} else {
2016-04-29 20:57:31 +08:00
// Find all alarm pixels in exclusive zones
2016-12-09 00:49:54 +08:00
for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) {
2016-04-29 20:57:31 +08:00
Zone *zone = zones[n_zone];
2016-12-09 00:49:54 +08:00
if ( !zone->IsExclusive() ) {
2016-04-29 20:57:31 +08:00
continue;
}
2019-04-16 00:55:28 +08:00
Debug(3, "Checking exclusive zone %s", zone->Label());
if ( zone->CheckAlarms(&delta_image) ) {
2016-04-29 20:57:31 +08:00
alarm = true;
score += zone->Score();
zone->SetAlarm();
2019-04-16 00:55:28 +08:00
Debug(3, "Zone is alarmed, zone score = %d", zone->Score());
zoneSet.insert(zone->Label());
2016-04-29 20:57:31 +08:00
}
2019-04-16 00:55:28 +08:00
} // end foreach zone
2017-02-19 04:22:56 +08:00
} // end if alarm or not
2019-04-16 00:55:28 +08:00
} // end if alarm
2016-04-29 20:57:31 +08:00
2016-12-09 00:49:54 +08:00
if ( top_score > 0 ) {
2016-04-29 20:57:31 +08:00
shared_data->alarm_x = alarm_centre.X();
shared_data->alarm_y = alarm_centre.Y();
2019-04-16 00:55:28 +08:00
Info("Got alarm centre at %d,%d, at count %d",
shared_data->alarm_x, shared_data->alarm_y, analysis_image_count);
2016-12-09 00:49:54 +08:00
} else {
2016-04-29 20:57:31 +08:00
shared_data->alarm_x = shared_data->alarm_y = -1;
}
// This is a small and innocent hack to prevent scores of 0 being returned in alarm state
2019-04-16 00:55:28 +08:00
return score ? score : alarm;
} // end MotionDetect
// TODO: Move the camera specific things to the camera classes and avoid these casts.
2019-04-16 00:55:28 +08:00
bool Monitor::DumpSettings(char *output, bool verbose) {
2016-04-29 20:57:31 +08:00
output[0] = 0;
2016-04-29 20:57:31 +08:00
sprintf( output+strlen(output), "Id : %d\n", id );
sprintf( output+strlen(output), "Name : %s\n", name );
sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") );
#if ZM_HAS_V4L
2016-12-09 00:49:54 +08:00
if ( camera->IsLocal() ) {
LocalCamera* cam = static_cast<LocalCamera*>(camera.get());
sprintf( output+strlen(output), "Device : %s\n", cam->Device().c_str() );
sprintf( output+strlen(output), "Channel : %d\n", cam->Channel() );
sprintf( output+strlen(output), "Standard : %d\n", cam->Standard() );
2016-12-09 00:49:54 +08:00
} else
#endif // ZM_HAS_V4L
2016-12-09 00:49:54 +08:00
if ( camera->IsRemote() ) {
RemoteCamera* cam = static_cast<RemoteCamera*>(camera.get());
sprintf( output+strlen(output), "Protocol : %s\n", cam->Protocol().c_str() );
sprintf( output+strlen(output), "Host : %s\n", cam->Host().c_str() );
sprintf( output+strlen(output), "Port : %s\n", cam->Port().c_str() );
sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() );
2016-12-09 00:49:54 +08:00
} else if ( camera->IsFile() ) {
FileCamera* cam = static_cast<FileCamera*>(camera.get());
sprintf( output+strlen(output), "Path : %s\n", cam->Path() );
2016-04-29 20:57:31 +08:00
}
#if HAVE_LIBAVFORMAT
2016-12-09 00:49:54 +08:00
else if ( camera->IsFfmpeg() ) {
FfmpegCamera* cam = static_cast<FfmpegCamera*>(camera.get());
sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() );
2016-04-29 20:57:31 +08:00
}
#endif // HAVE_LIBAVFORMAT
2016-04-29 20:57:31 +08:00
sprintf( output+strlen(output), "Width : %d\n", camera->Width() );
sprintf( output+strlen(output), "Height : %d\n", camera->Height() );
#if ZM_HAS_V4L
2016-12-09 00:49:54 +08:00
if ( camera->IsLocal() ) {
LocalCamera* cam = static_cast<LocalCamera*>(camera.get());
sprintf( output+strlen(output), "Palette : %d\n", cam->Palette() );
2016-04-29 20:57:31 +08:00
}
#endif // ZM_HAS_V4L
sprintf(output+strlen(output), "Colours : %d\n", camera->Colours() );
sprintf(output+strlen(output), "Subpixel Order : %d\n", camera->SubpixelOrder() );
sprintf(output+strlen(output), "Event Prefix : %s\n", event_prefix );
sprintf(output+strlen(output), "Label Format : %s\n", label_format );
sprintf(output+strlen(output), "Label Coord : %d,%d\n", label_coord.X(), label_coord.Y() );
sprintf(output+strlen(output), "Label Size : %d\n", label_size );
sprintf(output+strlen(output), "Image Buffer Count : %d\n", image_buffer_count );
sprintf(output+strlen(output), "Warmup Count : %d\n", warmup_count );
sprintf(output+strlen(output), "Pre Event Count : %d\n", pre_event_count );
sprintf(output+strlen(output), "Post Event Count : %d\n", post_event_count );
sprintf(output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer );
sprintf(output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count );
sprintf(output+strlen(output), "Section Length : %d\n", section_length);
sprintf(output+strlen(output), "Min Section Length : %d\n", min_section_length);
sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?(double)DT_PREC_3/capture_delay:0.0);
sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?(double)DT_PREC_3/alarm_capture_delay:0.0);
sprintf(output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc);
sprintf(output+strlen(output), "Alarm Reference Blend %%ge : %d\n", alarm_ref_blend_perc);
sprintf(output+strlen(output), "Track Motion : %d\n", track_motion);
sprintf(output+strlen(output), "Function: %d - %s\n", function,
2016-04-29 20:57:31 +08:00
function==NONE?"None":(
function==MONITOR?"Monitor Only":(
function==MODECT?"Motion Detection":(
function==RECORD?"Continuous Record":(
function==MOCORD?"Continuous Record with Motion Detection":(
function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown"
))))));
sprintf(output+strlen(output), "Zones : %d\n", n_zones );
2016-12-09 00:49:54 +08:00
for ( int i = 0; i < n_zones; i++ ) {
zones[i]->DumpSettings(output+strlen(output), verbose);
2016-04-29 20:57:31 +08:00
}
return true;
} // bool Monitor::DumpSettings(char *output, bool verbose)
unsigned int Monitor::Colours() const { return camera->Colours(); }
unsigned int Monitor::SubpixelOrder() const { return camera->SubpixelOrder(); }
2017-11-28 21:29:03 +08:00
int Monitor::PrimeCapture() {
2017-11-22 00:58:15 +08:00
int ret = camera->PrimeCapture();
if ( ret > 0 ) {
2017-12-01 20:26:34 +08:00
video_stream_id = camera->get_VideoStreamId();
packetqueue.addStreamId(video_stream_id);
audio_stream_id = camera->get_AudioStreamId();
if ( audio_stream_id >= 0 ) {
packetqueue.addStreamId(audio_stream_id);
shared_data->audio_frequency = camera->getFrequency();
shared_data->audio_channels = camera->getChannels();
}
2020-12-15 23:14:19 +08:00
Debug(2, "Video stream id is %d, audio is %d, minimum_packets to keep in buffer %d",
video_stream_id, audio_stream_id, pre_event_buffer_count);
if (rtsp_server) {
if (video_stream_id >= 0) {
AVStream *videoStream = camera->get_VideoStream();
snprintf(shared_data->video_fifo_path, sizeof(shared_data->video_fifo_path)-1, "%s/video_fifo_%d.%s",
staticConfig.PATH_SOCKS.c_str(),
id,
avcodec_get_name(videoStream->codecpar->codec_id));
video_fifo = new Fifo(shared_data->video_fifo_path, true);
}
if (record_audio and (audio_stream_id >= 0)) {
AVStream *audioStream = camera->get_AudioStream();
snprintf(shared_data->audio_fifo_path, sizeof(shared_data->audio_fifo_path)-1, "%s/video_fifo_%d.%s",
staticConfig.PATH_SOCKS.c_str(), id,
avcodec_get_name(audioStream->codecpar->codec_id));
audio_fifo = new Fifo(shared_data->audio_fifo_path, true);
}
} // end if rtsp_server
2020-12-15 23:14:19 +08:00
} else {
Debug(2, "Failed to prime %d", ret);
2017-12-01 20:26:34 +08:00
}
2017-11-22 00:58:15 +08:00
return ret;
} // end int Monitor::PrimeCapture()
int Monitor::PreCapture() const { return camera->PreCapture(); }
int Monitor::PostCapture() const { return camera->PostCapture(); }
int Monitor::Close() {
std::lock_guard<std::mutex> lck(event_mutex);
if (event) {
Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name, image_count, event->Id());
closeEvent();
}
2021-02-16 23:32:07 +08:00
if (camera) camera->Close();
packetqueue.clear();
return 1;
2021-02-16 23:32:07 +08:00
}
2016-10-12 21:12:09 +08:00
Monitor::Orientation Monitor::getOrientation() const { return orientation; }
// Wait for camera to get an image, and then assign it as the base reference image.
// So this should be done as the first task in the analysis thread startup.
2021-02-06 00:04:02 +08:00
// This function is deprecated.
void Monitor::get_ref_image() {
2021-02-06 00:04:02 +08:00
ZMPacket *snap = nullptr;
if ( !analysis_it )
analysis_it = packetqueue.get_video_it(true);
while (
(
!( snap = packetqueue.get_packet(analysis_it))
or
( snap->packet.stream_index != video_stream_id )
or
! snap->image
)
and !zm_terminate) {
2019-02-25 23:21:43 +08:00
Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%d)",
shared_data->last_write_index, shared_data->last_write_time);
2021-02-06 00:04:02 +08:00
if ( snap and ! snap->image ) {
snap->unlock();
// can't analyse it anyways, incremement
packetqueue.increment_it(analysis_it);
}
2019-02-25 23:21:43 +08:00
//usleep(10000);
}
2018-02-16 04:54:13 +08:00
if ( zm_terminate )
return;
Debug(1, "get_ref_image: packet.stream %d ?= video_stream %d, packet image id %d packet image %p",
snap->packet.stream_index, video_stream_id, snap->image_index, snap->image );
// Might not have been decoded yet FIXME
if ( snap->image ) {
ref_image.Assign(width, height, camera->Colours(),
camera->SubpixelOrder(), snap->image->Buffer(), camera->ImageSize());
Debug(2, "Have ref image about to unlock");
} else {
Debug(2, "Have no ref image about to unlock");
}
2019-02-25 23:21:43 +08:00
snap->unlock();
}
2019-04-16 00:55:28 +08:00
std::vector<Group *> Monitor::Groups() {
// At the moment, only load groups once.
2019-04-16 00:55:28 +08:00
if ( !groups.size() ) {
std::string sql = stringtf(
"SELECT `Id`, `ParentId`, `Name` FROM `Groups` WHERE `Groups.Id` IN "
"(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)", id);
MYSQL_RES *result = zmDbFetch(sql.c_str());
if ( !result ) {
Error("Can't load groups: %s", mysql_error(&dbconn));
return groups;
}
int n_groups = mysql_num_rows(result);
Debug(1, "Got %d groups", n_groups);
while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) {
groups.push_back(new Group(dbrow));
}
if ( mysql_errno(&dbconn) ) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
}
mysql_free_result(result);
}
return groups;
2019-04-16 00:55:28 +08:00
} // end Monitor::Groups()
StringVector Monitor::GroupNames() {
StringVector groupnames;
2019-04-16 00:55:28 +08:00
for ( Group * g: Groups() ) {
groupnames.push_back(std::string(g->Name()));
2019-04-16 00:55:28 +08:00
Debug(1,"Groups: %s", g->Name());
}
return groupnames;
2019-04-16 00:55:28 +08:00
} // end Monitor::GroupNames()