zoneminder/src/zm_event.cpp

1503 lines
48 KiB
C++
Raw Normal View History

2013-03-17 07:45:21 +08:00
//
// ZoneMinder Event 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.
2013-03-17 07:45:21 +08:00
//
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <getopt.h>
#include <arpa/inet.h>
#include <glob.h>
#include "zm.h"
#include "zm_db.h"
#include "zm_time.h"
#include "zm_mpeg.h"
#include "zm_signal.h"
#include "zm_event.h"
#include "zm_monitor.h"
2015-02-24 22:20:55 +08:00
// sendfile tricks
extern "C"
{
#include "zm_sendfile.h"
}
2013-03-17 07:45:21 +08:00
#include "zmf.h"
#if HAVE_SYS_SENDFILE_H
#include <sys/sendfile.h>
#endif
//#define USE_PREPARED_SQL 1
bool Event::initialised = false;
char Event::capture_file_format[PATH_MAX];
char Event::analyse_file_format[PATH_MAX];
char Event::general_file_format[PATH_MAX];
char Event::video_file_format[PATH_MAX];
2013-03-17 07:45:21 +08:00
int Event::pre_alarm_count = 0;
Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } };
Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) :
2016-04-29 21:11:14 +08:00
monitor( p_monitor ),
start_time( p_start_time ),
cause( p_cause ),
noteSetMap( p_noteSetMap ),
videoEvent( p_videoEvent ),
videowriter( NULL )
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
if ( !initialised )
Initialise();
std::string notes;
createNotes( notes );
bool untimedEvent = false;
if ( !start_time.tv_sec ) {
2016-04-29 21:11:14 +08:00
untimedEvent = true;
gettimeofday( &start_time, 0 );
}
Storage * storage = monitor->getStorage();
unsigned int state_id = 0;
zmDbRow dbrow;
if ( dbrow.fetch( "SELECT Id FROM States WHERE IsActive=1") ) {
state_id = atoi(dbrow[0]);
}
2016-04-29 21:11:14 +08:00
static char sql[ZM_SQL_MED_BUFSIZ];
2016-04-29 21:11:14 +08:00
struct tm *stime = localtime( &start_time.tv_sec );
2017-01-17 10:08:13 +08:00
snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed ) values ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d )",
monitor->Id(),
storage->Id(),
start_time.tv_sec,
monitor->Width(),
monitor->Height(),
cause.c_str(),
notes.c_str(),
state_id,
monitor->getOrientation(),
videoEvent
);
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql );
exit( mysql_errno( &dbconn ) );
}
id = mysql_insert_id( &dbconn );
if ( untimedEvent ) {
2016-04-29 21:11:14 +08:00
Warning( "Event %d has zero time, setting to current", id );
}
end_time.tv_sec = 0;
frames = 0;
alarm_frames = 0;
tot_score = 0;
max_score = 0;
2016-09-27 21:47:19 +08:00
char id_file[PATH_MAX];
struct stat statbuf;
if ( config.use_deep_storage ) {
2016-04-29 21:11:14 +08:00
char *path_ptr = path;
path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", storage->Path(), monitor->Id() );
int dt_parts[6];
dt_parts[0] = stime->tm_year-100;
dt_parts[1] = stime->tm_mon+1;
dt_parts[2] = stime->tm_mday;
dt_parts[3] = stime->tm_hour;
dt_parts[4] = stime->tm_min;
dt_parts[5] = stime->tm_sec;
char date_path[PATH_MAX] = "";
char time_path[PATH_MAX] = "";
char *time_path_ptr = time_path;
for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) {
2016-04-29 21:11:14 +08:00
path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
errno = 0;
2016-09-27 21:47:19 +08:00
// Do we really need to stat it? Perhaps we could do that on error, instead
2016-04-29 21:11:14 +08:00
if ( stat( path, &statbuf ) ) {
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir( path, 0755 ) ) {
2016-09-27 21:47:19 +08:00
// FIXME This should not be fatal. Should probably move to a different storage area.
2016-04-29 21:11:14 +08:00
Fatal( "Can't mkdir %s: %s", path, strerror(errno));
}
} else {
Warning( "Error stat'ing %s, may be fatal. error is %s", path, strerror(errno));
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
if ( i == 2 )
strncpy( date_path, path, sizeof(date_path) );
else if ( i >= 3 )
time_path_ptr += snprintf( time_path_ptr, sizeof(time_path)-(time_path_ptr-time_path), "%s%02d", i>3?"/":"", dt_parts[i] );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
// Create event id symlink
snprintf( id_file, sizeof(id_file), "%s/.%d", date_path, id );
if ( symlink( time_path, id_file ) < 0 )
Fatal( "Can't symlink %s -> %s: %s", id_file, path, strerror(errno));
// Create empty id tag file
} else {
2016-04-29 21:11:14 +08:00
snprintf( path, sizeof(path), "%s/%d/%d", storage->Path(), monitor->Id(), id );
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir( path, 0755 ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't mkdir %s: %s", path, strerror(errno));
}
2013-03-17 07:45:21 +08:00
}
2016-09-27 21:47:19 +08:00
} // deep storage or not
// Create empty id tag file
snprintf( id_file, sizeof(id_file), "%s/.%d", path, id );
if ( FILE *id_fp = fopen( id_file, "w" ) )
fclose( id_fp );
else
Fatal( "Can't fopen %s: %s", id_file, strerror(errno));
2016-04-29 21:11:14 +08:00
last_db_frame = 0;
2016-04-29 21:11:14 +08:00
video_name[0] = 0;
2016-04-29 21:11:14 +08:00
/* Save as video */
2016-04-29 21:11:14 +08:00
if ( monitor->GetOptVideoWriter() != 0 ) {
snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" );
snprintf( video_file, sizeof(video_file), video_file_format, path, video_name );
2016-04-29 21:11:14 +08:00
/* X264 MP4 video writer */
if(monitor->GetOptVideoWriter() == 1) {
#if ZM_HAVE_VIDEOWRITER_X264MP4
2016-04-29 21:11:14 +08:00
videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams());
#else
2016-04-29 21:11:14 +08:00
videowriter = NULL;
Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)");
#endif
2016-04-29 21:11:14 +08:00
}
if(videowriter != NULL) {
2016-09-27 21:47:19 +08:00
2016-04-29 21:11:14 +08:00
/* Open the video stream */
int nRet = videowriter->Open();
2016-04-29 21:11:14 +08:00
if(nRet != 0) {
Error("Failed opening video stream");
delete videowriter;
videowriter = NULL;
}
2016-09-27 21:47:19 +08:00
snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" );
snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name );
2016-04-29 21:11:14 +08:00
/* Create timecodes file */
timecodes_fd = fopen(timecodes_file, "wb");
if(timecodes_fd == NULL) {
Error("Failed creating timecodes file");
}
}
} else {
/* No video object */
videowriter = NULL;
}
} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent )
2013-03-17 07:45:21 +08:00
Event::~Event() {
2016-09-27 21:47:19 +08:00
static char sql[ZM_SQL_MED_BUFSIZ];
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 );
if ( frames > last_db_frame ) {
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Debug( 1, "Adding closing frame %d to DB", frames );
snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )", id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't insert frame: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
/* Close the video file */
if ( videowriter != NULL ) {
int nRet;
2016-04-29 21:11:14 +08:00
nRet = videowriter->Close();
if(nRet != 0) {
Error("Failed closing video stream");
}
delete videowriter;
videowriter = NULL;
2016-04-29 21:11:14 +08:00
/* Close the timecodes file */
fclose(timecodes_fd);
timecodes_fd = NULL;
}
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id );
if ( mysql_query( &dbconn, sql ) )
{
Error( "Can't update event: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
2013-03-17 07:45:21 +08:00
}
void Event::createNotes( std::string &notes ) {
2016-04-29 21:11:14 +08:00
notes.clear();
for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) {
2016-04-29 21:11:14 +08:00
notes += mapIter->first;
notes += ": ";
const StringSet &stringSet = mapIter->second;
for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) {
2016-04-29 21:11:14 +08:00
if ( setIter != stringSet.begin() )
notes += ", ";
notes += *setIter;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
}
int Event::sd = -1;
bool Event::OpenFrameSocket( int monitor_id ) {
if ( sd > 0 ) {
2016-04-29 21:11:14 +08:00
close( sd );
}
sd = socket( AF_UNIX, SOCK_STREAM, 0 );
if ( sd < 0 ) {
2016-04-29 21:11:14 +08:00
Error( "Can't create socket: %s", strerror(errno) );
return( false );
}
int socket_buffer_size = config.frame_socket_size;
if ( socket_buffer_size > 0 ) {
if ( setsockopt( sd, SOL_SOCKET, SO_SNDBUF, &socket_buffer_size, sizeof(socket_buffer_size) ) < 0 ) {
2016-04-29 21:11:14 +08:00
Error( "Can't get socket buffer size to %d, error = %s", socket_buffer_size, strerror(errno) );
close( sd );
sd = -1;
return( false );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
int flags;
if ( (flags = fcntl( sd, F_GETFL )) < 0 ) {
2016-04-29 21:11:14 +08:00
Error( "Can't get socket flags, error = %s", strerror(errno) );
close( sd );
sd = -1;
return( false );
}
flags |= O_NONBLOCK;
if ( fcntl( sd, F_SETFL, flags ) < 0 ) {
2016-04-29 21:11:14 +08:00
Error( "Can't set socket flags, error = %s", strerror(errno) );
close( sd );
sd = -1;
return( false );
}
char sock_path[PATH_MAX] = "";
snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id );
struct sockaddr_un addr;
strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) );
addr.sun_family = AF_UNIX;
if ( connect( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)+1) < 0 ) {
2016-04-29 21:11:14 +08:00
Warning( "Can't connect to frame server: %s", strerror(errno) );
close( sd );
sd = -1;
return( false );
}
Debug( 1, "Opened connection to frame server" );
return( true );
2013-03-17 07:45:21 +08:00
}
bool Event::ValidateFrameSocket( int monitor_id ) {
if ( sd < 0 ) {
2016-04-29 21:11:14 +08:00
return( OpenFrameSocket( monitor_id ) );
}
return( true );
2013-03-17 07:45:21 +08:00
}
bool Event::SendFrameImage( const Image *image, bool alarm_frame ) {
if ( !ValidateFrameSocket( monitor->Id() ) ) {
2016-04-29 21:11:14 +08:00
return( false );
}
static int jpg_buffer_size = 0;
static unsigned char jpg_buffer[ZM_MAX_IMAGE_SIZE];
image->EncodeJpeg( jpg_buffer, &jpg_buffer_size, (alarm_frame&&(config.jpeg_alarm_file_quality>config.jpeg_file_quality))?config.jpeg_alarm_file_quality:config.jpeg_file_quality );
static FrameHeader frame_header;
frame_header.event_id = id;
if ( config.use_deep_storage )
frame_header.event_time = start_time.tv_sec;
frame_header.frame_id = frames;
frame_header.alarm_frame = alarm_frame;
frame_header.image_length = jpg_buffer_size;
struct iovec iovecs[2];
iovecs[0].iov_base = &frame_header;
iovecs[0].iov_len = sizeof(frame_header);
iovecs[1].iov_base = jpg_buffer;
iovecs[1].iov_len = jpg_buffer_size;
ssize_t writev_size = sizeof(frame_header)+jpg_buffer_size;
ssize_t writev_result = writev( sd, iovecs, sizeof(iovecs)/sizeof(*iovecs));
if ( writev_result != writev_size ) {
if ( writev_result < 0 ) {
if ( errno == EAGAIN ) {
2016-04-29 21:11:14 +08:00
Warning( "Blocking write detected" );
} else {
2016-04-29 21:11:14 +08:00
Error( "Can't write frame: %s", strerror(errno) );
close( sd );
sd = -1;
}
} else {
2016-04-29 21:11:14 +08:00
Error( "Incomplete frame write: %zd of %zd bytes written", writev_result, writev_size );
close( sd );
sd = -1;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
return( false );
}
Debug( 1, "Wrote frame image, %d bytes", jpg_buffer_size );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
return( true );
2013-03-17 07:45:21 +08:00
}
bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) {
2016-04-29 21:11:14 +08:00
Image* ImgToWrite;
Image* ts_image = NULL;
if ( config.timestamp_on_capture ) // stash the image we plan to use in another pointer regardless if timestamped.
{
ts_image = new Image(*image);
monitor->TimestampImage( ts_image, &timestamp );
ImgToWrite=ts_image;
} else
2016-04-29 21:11:14 +08:00
ImgToWrite=image;
if ( !config.opt_frame_server || !SendFrameImage(ImgToWrite, alarm_frame) ) {
2016-04-29 21:11:14 +08:00
int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default
ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write
}
if(ts_image) delete(ts_image); // clean up if used.
return( true );
2013-03-17 07:45:21 +08:00
}
bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ) {
2016-04-29 21:11:14 +08:00
const Image* frameimg = image;
Image ts_image;
/* Checking for invalid parameters */
if ( videow == NULL ) {
Error("NULL Video object");
return false;
}
/* If the image does not contain a timestamp, add the timestamp */
if (!config.timestamp_on_capture) {
ts_image = *image;
monitor->TimestampImage( &ts_image, &timestamp );
frameimg = &ts_image;
}
/* Calculate delta time */
struct DeltaTimeval delta_time3;
DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 );
unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec;
/* Encode and write the frame */
if(videowriter->Encode(frameimg, timeMS) != 0) {
Error("Failed encoding video frame");
}
/* Add the frame to the timecodes file */
fprintf(timecodes_fd, "%u\n", timeMS);
return( true );
}
void Event::updateNotes( const StringSetMap &newNoteSetMap ) {
2016-04-29 21:11:14 +08:00
bool update = false;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
//Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() );
if ( newNoteSetMap.size() > 0 ) {
if ( noteSetMap.size() == 0 ) {
2016-04-29 21:11:14 +08:00
noteSetMap = newNoteSetMap;
update = true;
} else {
for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) {
2016-04-29 21:11:14 +08:00
const std::string &newNoteGroup = newNoteSetMapIter->first;
const StringSet &newNoteSet = newNoteSetMapIter->second;
//Info( "Got %d new strings", newNoteSet.size() );
if ( newNoteSet.size() > 0 ) {
2016-04-29 21:11:14 +08:00
StringSetMap::iterator noteSetMapIter = noteSetMap.find( newNoteGroup );
if ( noteSetMapIter == noteSetMap.end() ) {
2016-04-29 21:11:14 +08:00
//Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() );
noteSetMap.insert( StringSetMap::value_type( newNoteGroup, newNoteSet ) );
2013-03-17 07:45:21 +08:00
update = true;
} else {
2016-04-29 21:11:14 +08:00
StringSet &noteSet = noteSetMapIter->second;
//Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() );
for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) {
2016-04-29 21:11:14 +08:00
const std::string &newNote = *newNoteSetIter;
StringSet::iterator noteSetIter = noteSet.find( newNote );
if ( noteSetIter == noteSet.end() ) {
2016-04-29 21:11:14 +08:00
noteSet.insert( newNote );
update = true;
}
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
}
} // end for
} // end if ( noteSetMap.size() == 0
} // end if newNoteSetupMap.size() > 0
2013-03-17 07:45:21 +08:00
if ( update ) {
2016-04-29 21:11:14 +08:00
std::string notes;
createNotes( notes );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Debug( 2, "Updating notes for event %d, '%s'", id, notes.c_str() );
static char sql[ZM_SQL_MED_BUFSIZ];
2013-03-17 07:45:21 +08:00
#if USE_PREPARED_SQL
2016-04-29 21:11:14 +08:00
static MYSQL_STMT *stmt = 0;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
char notesStr[ZM_SQL_MED_BUFSIZ] = "";
unsigned long notesLen = 0;
2013-03-17 07:45:21 +08:00
if ( !stmt ) {
2016-04-29 21:11:14 +08:00
const char *sql = "update Events set Notes = ? where Id = ?";
stmt = mysql_stmt_init( &dbconn );
if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) {
2016-04-29 21:11:14 +08:00
Fatal( "Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt) );
}
/* Get the parameter count from the statement */
if ( mysql_stmt_param_count( stmt ) != 2 ) {
2016-04-29 21:11:14 +08:00
Fatal( "Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count( stmt ), sql );
}
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
/* STRING PARAM */
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (char *)notesStr;
bind[0].buffer_length = sizeof(notesStr);
bind[0].is_null = 0;
bind[0].length = &notesLen;
bind[1].buffer_type= MYSQL_TYPE_LONG;
bind[1].buffer= (char *)&id;
bind[1].is_null= 0;
bind[1].length= 0;
/* Bind the buffers */
if ( mysql_stmt_bind_param( stmt, bind ) ) {
2016-04-29 21:11:14 +08:00
Fatal( "Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt) );
}
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
strncpy( notesStr, notes.c_str(), sizeof(notesStr) );
notesLen = notes.length();
2013-03-17 07:45:21 +08:00
if ( mysql_stmt_execute( stmt ) ) {
2016-04-29 21:11:14 +08:00
Fatal( "Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt) );
}
2013-03-17 07:45:21 +08:00
#else
2016-04-29 21:11:14 +08:00
static char escapedNotes[ZM_SQL_MED_BUFSIZ];
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "update Events set Notes = '%s' where Id = %d", escapedNotes, id );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't insert event: %s", mysql_error( &dbconn ) );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
#endif
}
2013-03-17 07:45:21 +08:00
}
void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) {
2016-04-29 21:11:14 +08:00
for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) {
AddFramesInternal(n_frames, i, images, timestamps);
}
}
void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) {
2016-04-29 21:11:14 +08:00
static char sql[ZM_SQL_LGE_BUFSIZ];
strncpy( sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql) );
int frameCount = 0;
for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) {
if ( !timestamps[i]->tv_sec ) {
2016-04-29 21:11:14 +08:00
Debug( 1, "Not adding pre-capture frame %d, zero timestamp", i );
continue;
2013-03-17 07:45:21 +08:00
}
frames++;
static char event_file[PATH_MAX];
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
if ( monitor->GetOptSaveJPEGs() & 4) {
2016-04-29 21:11:14 +08:00
//If this is the first frame, we should add a thumbnail to the event directory
if(frames == 10){
char snapshot_file[PATH_MAX];
snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path );
WriteFrameImage( images[i], *(timestamps[i]), snapshot_file );
}
}
2016-04-29 21:11:14 +08:00
if ( monitor->GetOptSaveJPEGs() & 1) {
Debug( 1, "Writing pre-capture frame %d", frames );
WriteFrameImage( images[i], *(timestamps[i]), event_file );
}
if ( videowriter != NULL ) {
2016-04-29 21:11:14 +08:00
WriteFrameVideo( images[i], *(timestamps[i]), videowriter );
}
2013-03-17 07:45:21 +08:00
struct DeltaTimeval delta_time;
2016-04-29 21:11:14 +08:00
DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
int sql_len = strlen(sql);
snprintf( sql+sql_len, sizeof(sql)-sql_len, "( %d, %d, from_unixtime(%ld), %s%ld.%02ld ), ", id, frames, timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
frameCount++;
}
2013-03-17 07:45:21 +08:00
if ( frameCount ) {
2016-04-29 21:11:14 +08:00
Debug( 1, "Adding %d/%d frames to DB", frameCount, n_frames );
*(sql+strlen(sql)-2) = '\0';
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't insert frames: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
last_db_frame = frames;
} else {
2016-04-29 21:11:14 +08:00
Debug( 1, "No valid pre-capture frames to add" );
}
2013-03-17 07:45:21 +08:00
}
void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) {
if ( !timestamp.tv_sec ) {
2016-04-29 21:11:14 +08:00
Debug( 1, "Not adding new frame, zero timestamp" );
return;
}
frames++;
static char event_file[PATH_MAX];
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
if ( monitor->GetOptSaveJPEGs() & 4) {
//If this is the first frame, we should add a thumbnail to the event directory
if(frames == 10){
char snapshot_file[PATH_MAX];
snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path );
WriteFrameImage( image, timestamp, snapshot_file );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
if( monitor->GetOptSaveJPEGs() & 1) {
Debug( 1, "Writing capture frame %d", frames );
WriteFrameImage( image, timestamp, event_file );
}
if ( videowriter != NULL ) {
WriteFrameVideo( image, timestamp, videowriter );
}
struct DeltaTimeval delta_time;
DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 );
const char *frame_type = score>0?"Alarm":(score<0?"Bulk":"Normal");
if ( score < 0 )
score = 0;
bool db_frame = (strcmp(frame_type,"Bulk") != 0) || ((frames%config.bulk_frame_interval)==0) || !frames;
if ( db_frame ) {
2016-04-29 21:11:14 +08:00
Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type );
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type, timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't insert frame: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
last_db_frame = frames;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// We are writing a Bulk frame
if ( !strcmp( frame_type,"Bulk") ) {
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't update event: %s", mysql_error( &dbconn ) );
2013-03-17 07:45:21 +08:00
exit( mysql_errno( &dbconn ) );
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
end_time = timestamp;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// We are writing an Alarm frame
if ( !strcmp( frame_type,"Alarm") ) {
2016-04-29 21:11:14 +08:00
alarm_frames++;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
tot_score += score;
if ( score > (int)max_score )
max_score = score;
2013-03-17 07:45:21 +08:00
if ( alarm_image ) {
2016-04-29 21:11:14 +08:00
snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Debug( 1, "Writing analysis frame %d", frames );
if ( monitor->GetOptSaveJPEGs() & 2) {
WriteFrameImage( alarm_image, timestamp, event_file, true );
}
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
/* This makes viewing the diagnostic images impossible because it keeps deleting them
if ( config.record_diag_images )
{
char diag_glob[PATH_MAX] = "";
snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", config.dir_events, monitor->Id() );
glob_t pglob;
int glob_status = glob( diag_glob, 0, 0, &pglob );
if ( glob_status != 0 )
{
if ( glob_status < 0 )
{
Error( "Can't glob '%s': %s", diag_glob, strerror(errno) );
}
else
{
Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status );
}
}
else
{
char new_diag_path[PATH_MAX] = "";
for ( int i = 0; i < pglob.gl_pathc; i++ )
{
char *diag_path = pglob.gl_pathv[i];
char *diag_file = strstr( diag_path, "diag-" );
if ( diag_file )
{
snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file );
if ( rename( diag_path, new_diag_path ) < 0 )
{
Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) );
}
}
}
}
globfree( &pglob );
}
*/
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
bool EventStream::loadInitialEventData( int monitor_id, time_t event_time )
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
static char sql[ZM_SQL_SML_BUFSIZ];
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %d and unix_timestamp( EndTime ) > %ld order by Id asc limit 1", monitor_id, event_time );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if ( mysql_query( &dbconn, sql ) )
{
Error( "Can't run query: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result ) {
2016-04-29 21:11:14 +08:00
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
MYSQL_ROW dbrow = mysql_fetch_row( result );
2013-03-17 07:45:21 +08:00
if ( mysql_errno( &dbconn ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
2016-04-29 21:11:14 +08:00
int init_event_id = atoi( dbrow[0] );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
mysql_free_result( result );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
loadEventData( init_event_id );
2013-03-17 07:45:21 +08:00
if ( event_time ) {
2016-04-29 21:11:14 +08:00
curr_stream_time = event_time;
curr_frame_id = 1;
if ( event_time >= event_data->start_time ) {
for (unsigned int i = 0; i < event_data->frame_count; i++ ) {
2016-04-29 21:11:14 +08:00
//Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time );
if ( event_data->frames[i].timestamp >= event_time ) {
2016-04-29 21:11:14 +08:00
curr_frame_id = i+1;
Debug( 3, "Set cst:%.2f", curr_stream_time );
Debug( 3, "Set cfid:%d", curr_frame_id );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
Debug( 3, "Skipping %ld frames", event_data->frame_count );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
return( true );
}
2013-03-17 07:45:21 +08:00
bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) {
2016-04-29 21:11:14 +08:00
loadEventData( init_event_id );
if ( init_frame_id ) {
2016-04-29 21:11:14 +08:00
curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
curr_frame_id = init_frame_id;
} else {
2016-04-29 21:11:14 +08:00
curr_stream_time = event_data->start_time;
}
return( true );
}
2013-03-17 07:45:21 +08:00
bool EventStream::loadEventData( int event_id ) {
2016-04-29 21:11:14 +08:00
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.StorageId, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=E.Id) as Duration,E.DefaultVideo from Events as E inner join Monitors as M on E.MonitorId = M.Id where E.Id = %d", event_id );
2016-04-29 21:11:14 +08:00
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't run query: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result ) {
2016-04-29 21:11:14 +08:00
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
if ( !mysql_num_rows( result ) ) {
2016-04-29 21:11:14 +08:00
Fatal( "Unable to load event %d, not found in DB", event_id );
}
MYSQL_ROW dbrow = mysql_fetch_row( result );
if ( mysql_errno( &dbconn ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
delete event_data;
event_data = new EventData;
event_data->event_id = event_id;
event_data->monitor_id = atoi( dbrow[0] );
event_data->storage_id = dbrow[2] ? atoi( dbrow[2] ) : 0;
event_data->start_time = atoi(dbrow[4]);
Storage * storage = new Storage( event_data->storage_id );
const char *storage_path = storage->Path();
if ( config.use_deep_storage ) {
2016-04-29 21:11:14 +08:00
struct tm *event_time = localtime( &event_data->start_time );
if ( storage_path[0] == '/' )
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
else
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
} else {
2016-04-29 21:11:14 +08:00
if ( storage_path[0] == '/' )
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", storage_path, event_data->monitor_id, event_data->event_id );
else
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id );
}
event_data->frame_count = dbrow[3] == NULL ? 0 : atoi(dbrow[3]);
event_data->duration = atof(dbrow[5]);
strncpy( event_data->video_file, dbrow[6], sizeof( event_data->video_file )-1 );
updateFrameRate( (double)event_data->frame_count/event_data->duration );
mysql_free_result( result );
snprintf( sql, sizeof(sql), "select FrameId, unix_timestamp( `TimeStamp` ), Delta from Frames where EventId = %d order by FrameId asc", event_id );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't run query: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
result = mysql_store_result( &dbconn );
if ( !result ) {
2016-04-29 21:11:14 +08:00
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
event_data->n_frames = mysql_num_rows( result );
event_data->frames = new FrameData[event_data->frame_count];
int id, last_id = 0;
time_t timestamp, last_timestamp = event_data->start_time;
double delta, last_delta = 0.0;
while ( ( dbrow = mysql_fetch_row( result ) ) ) {
2016-04-29 21:11:14 +08:00
id = atoi(dbrow[0]);
timestamp = atoi(dbrow[1]);
delta = atof(dbrow[2]);
int id_diff = id - last_id;
double frame_delta = (delta-last_delta)/id_diff;
if ( id_diff > 1 ) {
for ( int i = last_id+1; i < id; i++ ) {
2016-04-29 21:11:14 +08:00
event_data->frames[i-1].timestamp = (time_t)(last_timestamp + ((i-last_id)*frame_delta));
event_data->frames[i-1].offset = (time_t)(event_data->frames[i-1].timestamp-event_data->start_time);
event_data->frames[i-1].delta = frame_delta;
event_data->frames[i-1].in_db = false;
}
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
event_data->frames[id-1].timestamp = timestamp;
event_data->frames[id-1].offset = (time_t)(event_data->frames[id-1].timestamp-event_data->start_time);
event_data->frames[id-1].delta = id>1?frame_delta:0.0;
event_data->frames[id-1].in_db = true;
last_id = id;
last_delta = delta;
last_timestamp = timestamp;
}
if ( mysql_errno( &dbconn ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
//for ( int i = 0; i < 250; i++ )
//{
//Info( "%d -> %d @ %f (%d)", i+1, event_data->frames[i].timestamp, event_data->frames[i].delta, event_data->frames[i].in_db );
//}
mysql_free_result( result );
if ( forceEventChange || mode == MODE_ALL_GAPLESS ) {
2016-04-29 21:11:14 +08:00
if ( replay_rate > 0 )
curr_stream_time = event_data->frames[0].timestamp;
else
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
}
Debug( 2, "Event:%ld, Frames:%ld, Duration: %.2f", event_data->event_id, event_data->frame_count, event_data->duration );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
return( true );
2013-03-17 07:45:21 +08:00
}
void EventStream::processCommand( const CmdMsg *msg ) {
2016-04-29 21:11:14 +08:00
Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] )
2013-03-17 07:45:21 +08:00
// Check for incoming command
switch( (MsgCommand)msg->msg_data[0] ) {
2016-04-29 21:11:14 +08:00
case CMD_PAUSE :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got PAUSE command" );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// Set paused flag
paused = true;
replay_rate = ZM_RATE_BASE;
last_frame_sent = TV_2_FLOAT( now );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_PLAY :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got PLAY command" );
if ( paused )
{
// Clear paused flag
paused = false;
}
2014-03-31 00:18:14 +08:00
2016-04-29 21:11:14 +08:00
// If we are in single event mode and at the last frame, replay the current event
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) )
curr_frame_id = 1;
2014-03-31 00:18:14 +08:00
2016-04-29 21:11:14 +08:00
replay_rate = ZM_RATE_BASE;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_VARPLAY :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got VARPLAY command" );
if ( paused )
{
// Clear paused flag
paused = false;
}
replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_STOP :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got STOP command" );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// Clear paused flag
paused = false;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_FASTFWD :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got FAST FWD command" );
if ( paused )
{
// Clear paused flag
paused = false;
}
// Set play rate
switch ( replay_rate )
{
case 2 * ZM_RATE_BASE :
replay_rate = 5 * ZM_RATE_BASE;
break;
case 5 * ZM_RATE_BASE :
replay_rate = 10 * ZM_RATE_BASE;
break;
case 10 * ZM_RATE_BASE :
replay_rate = 25 * ZM_RATE_BASE;
break;
case 25 * ZM_RATE_BASE :
case 50 * ZM_RATE_BASE :
replay_rate = 50 * ZM_RATE_BASE;
break;
default :
replay_rate = 2 * ZM_RATE_BASE;
break;
}
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_SLOWFWD :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got SLOW FWD command" );
// Set paused flag
paused = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = 1;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_SLOWREV :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got SLOW REV command" );
// Set paused flag
paused = true;
// Set play rate
replay_rate = ZM_RATE_BASE;
// Set step
step = -1;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_FASTREV :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got FAST REV command" );
if ( paused )
{
// Clear paused flag
paused = false;
}
// Set play rate
switch ( replay_rate )
{
case -2 * ZM_RATE_BASE :
replay_rate = -5 * ZM_RATE_BASE;
break;
case -5 * ZM_RATE_BASE :
replay_rate = -10 * ZM_RATE_BASE;
break;
case -10 * ZM_RATE_BASE :
replay_rate = -25 * ZM_RATE_BASE;
break;
case -25 * ZM_RATE_BASE :
case -50 * ZM_RATE_BASE :
replay_rate = -50 * ZM_RATE_BASE;
break;
default :
replay_rate = -2 * ZM_RATE_BASE;
break;
}
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_ZOOMIN :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got ZOOM IN command, to %d,%d", x, y );
switch ( zoom )
{
case 100:
zoom = 150;
break;
case 150:
zoom = 200;
break;
case 200:
zoom = 300;
break;
case 300:
zoom = 400;
break;
case 400:
default :
zoom = 500;
break;
}
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_ZOOMOUT :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got ZOOM OUT command" );
switch ( zoom )
{
case 500:
zoom = 400;
break;
case 400:
zoom = 300;
break;
case 300:
zoom = 200;
break;
case 200:
zoom = 150;
break;
case 150:
default :
zoom = 100;
break;
}
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_PAN :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
Debug( 1, "Got PAN command, to %d,%d", x, y );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_SCALE :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
Debug( 1, "Got SCALE command, to %d", scale );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_PREV :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got PREV command" );
if ( replay_rate >= 0 )
curr_frame_id = 0;
else
curr_frame_id = event_data->frame_count+1;
paused = false;
forceEventChange = true;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_NEXT :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got NEXT command" );
if ( replay_rate >= 0 )
curr_frame_id = event_data->frame_count+1;
else
curr_frame_id = 0;
paused = false;
forceEventChange = true;
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_SEEK :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration);
Debug( 1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_QUERY :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Debug( 1, "Got QUERY command, sending STATUS" );
break;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
case CMD_QUIT :
2016-01-29 22:58:50 +08:00
{
2016-04-29 21:11:14 +08:00
Info ("User initiated exit - CMD_QUIT");
break;
2016-01-29 22:58:50 +08:00
}
2016-04-29 21:11:14 +08:00
default :
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
// Do nothing, for now
2013-03-17 07:45:21 +08:00
}
}
2016-04-29 21:11:14 +08:00
struct {
int event;
int progress;
int rate;
int zoom;
bool paused;
} status_data;
status_data.event = event_data->event_id;
status_data.progress = (int)event_data->frames[curr_frame_id-1].offset;
status_data.rate = replay_rate;
status_data.zoom = zoom;
status_data.paused = paused;
Debug( 2, "E:%d, P:%d, p:%d R:%d, Z:%d",
status_data.event,
status_data.paused,
status_data.progress,
status_data.rate,
status_data.zoom
);
DataMsg status_msg;
status_msg.msg_type = MSG_DATA_EVENT;
memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) );
if ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) {
2016-04-29 21:11:14 +08:00
//if ( errno != EAGAIN )
2013-03-17 07:45:21 +08:00
{
2016-04-29 21:11:14 +08:00
Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
exit( -1 );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
// quit after sending a status, if this was a quit request
if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
exit(0);
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
updateFrameRate( (double)event_data->frame_count/event_data->duration );
2013-03-17 07:45:21 +08:00
}
void EventStream::checkEventLoaded() {
2016-04-29 21:11:14 +08:00
bool reload_event = false;
static char sql[ZM_SQL_SML_BUFSIZ];
if ( curr_frame_id <= 0 ) {
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id < %ld order by Id desc limit 1", event_data->monitor_id, event_data->event_id );
reload_event = true;
} else if ( (unsigned int)curr_frame_id > event_data->frame_count ) {
2016-04-29 21:11:14 +08:00
snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id > %ld order by Id asc limit 1", event_data->monitor_id, event_data->event_id );
reload_event = true;
}
if ( reload_event ) {
if ( forceEventChange || mode != MODE_SINGLE ) {
2016-04-29 21:11:14 +08:00
//Info( "SQL:%s", sql );
if ( mysql_query( &dbconn, sql ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't run query: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result ) {
2016-04-29 21:11:14 +08:00
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
MYSQL_ROW dbrow = mysql_fetch_row( result );
2013-03-17 07:45:21 +08:00
if ( mysql_errno( &dbconn ) ) {
2016-04-29 21:11:14 +08:00
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
2013-03-17 07:45:21 +08:00
if ( dbrow ) {
2016-04-29 21:11:14 +08:00
int event_id = atoi(dbrow[0]);
Debug( 1, "Loading new event %d", event_id );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
loadEventData( event_id );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Debug( 2, "Current frame id = %d", curr_frame_id );
if ( replay_rate < 0 )
curr_frame_id = event_data->frame_count;
2013-03-17 07:45:21 +08:00
else
2016-04-29 21:11:14 +08:00
curr_frame_id = 1;
Debug( 2, "New frame id = %d", curr_frame_id );
} else {
2016-04-29 21:11:14 +08:00
if ( curr_frame_id <= 0 )
curr_frame_id = 1;
else
curr_frame_id = event_data->frame_count;
paused = true;
}
mysql_free_result( result );
forceEventChange = false;
} else {
2016-04-29 21:11:14 +08:00
if ( curr_frame_id <= 0 )
curr_frame_id = 1;
else
curr_frame_id = event_data->frame_count;
paused = true;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
}
Image * EventStream::getImage( ) {
2016-04-29 21:11:14 +08:00
Event::Initialise();
static char filepath[PATH_MAX];
Debug( 2, "EventStream::getImage path(%s) frame(%d)", event_data->path, curr_frame_id );
snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id );
Debug( 2, "EventStream::getImage path(%s) ", filepath, curr_frame_id );
Image *image = new Image( filepath );
return image;
}
bool EventStream::sendFrame( int delta_us ) {
2016-04-29 21:11:14 +08:00
Debug( 2, "Sending frame %d", curr_frame_id );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
static char filepath[PATH_MAX];
static struct stat filestat;
FILE *fdj = NULL;
2016-09-29 21:28:48 +08:00
if ( monitor->GetOptSaveJPEGs() & 1) {
2016-04-29 21:11:14 +08:00
snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id );
2016-09-29 21:28:48 +08:00
} else if ( monitor->GetOptSaveJPEGs() & 2 ) {
snprintf( filepath, sizeof(filepath), Event::analyse_file_format, event_data->path, curr_frame_id );
} else {
Fatal("JPEGS not saved.zms is not capable of streaming jpegs from mp4 yet");
return false;
}
2013-03-17 07:45:21 +08:00
#if HAVE_LIBAVCODEC
if ( type == STREAM_MPEG ) {
2016-04-29 21:11:14 +08:00
Image image( filepath );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Image *send_image = prepareImage( &image );
2013-03-17 07:45:21 +08:00
if ( !vid_stream ) {
2016-04-29 21:11:14 +08:00
vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() );
fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() );
vid_stream->OpenStream();
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
/* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000 );
} else
2013-03-17 07:45:21 +08:00
#endif // HAVE_LIBAVCODEC
2016-04-29 21:11:14 +08:00
{
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
int img_buffer_size = 0;
uint8_t *img_buffer = temp_img_buffer;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
fprintf( stdout, "--ZoneMinderFrame\r\n" );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if ( type != STREAM_JPEG )
send_raw = false;
2013-03-17 07:45:21 +08:00
if ( send_raw ) {
2016-04-29 21:11:14 +08:00
fdj = fopen( filepath, "rb" );
if ( !fdj ) {
2016-04-29 21:11:14 +08:00
Error( "Can't open %s: %s", filepath, strerror(errno) );
return( false );
}
2013-03-17 07:45:21 +08:00
#if HAVE_SENDFILE
2016-04-29 21:11:14 +08:00
if( fstat(fileno(fdj),&filestat) < 0 ) {
Error( "Failed getting information about file %s: %s", filepath, strerror(errno) );
return( false );
}
2013-03-17 07:45:21 +08:00
#else
2016-04-29 21:11:14 +08:00
img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj );
2013-03-17 07:45:21 +08:00
#endif
} else {
2016-04-29 21:11:14 +08:00
Image image( filepath );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
Image *send_image = prepareImage( &image );
2013-03-17 07:45:21 +08:00
switch( type ) {
2016-04-29 21:11:14 +08:00
case STREAM_JPEG :
send_image->EncodeJpeg( img_buffer, &img_buffer_size );
break;
case STREAM_ZIP :
#if HAVE_ZLIB_H
2016-04-29 21:11:14 +08:00
unsigned long zip_buffer_size;
send_image->Zip( img_buffer, &zip_buffer_size );
img_buffer_size = zip_buffer_size;
break;
#else
2016-04-29 21:11:14 +08:00
Error("zlib is required for zipped images. Falling back to raw image");
type = STREAM_RAW;
#endif // HAVE_ZLIB_H
2016-04-29 21:11:14 +08:00
case STREAM_RAW :
img_buffer = (uint8_t*)(send_image->Buffer());
img_buffer_size = send_image->Size();
break;
default:
Fatal( "Unexpected frame type %d", type );
break;
}
}
2013-03-17 07:45:21 +08:00
switch( type ) {
2016-04-29 21:11:14 +08:00
case STREAM_JPEG :
fprintf( stdout, "Content-Type: image/jpeg\r\n" );
break;
case STREAM_RAW :
fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
break;
case STREAM_ZIP :
fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
break;
default :
Fatal( "Unexpected frame type %d", type );
break;
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if(send_raw) {
2013-03-17 07:45:21 +08:00
#if HAVE_SENDFILE
2016-04-29 21:11:14 +08:00
fprintf( stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size );
if(zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size) {
/* sendfile() failed, use standard way instead */
img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno));
return( false );
}
}
2013-03-17 07:45:21 +08:00
#else
2016-04-29 21:11:14 +08:00
fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno));
return( false );
}
#endif
fclose(fdj); /* Close the file handle */
} else {
fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size );
if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) {
2016-04-29 21:11:14 +08:00
Error( "Unable to send stream frame: %s", strerror(errno) );
return( false );
}
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
fprintf( stdout, "\r\n\r\n" );
fflush( stdout );
}
last_frame_sent = TV_2_FLOAT( now );
return( true );
2013-03-17 07:45:21 +08:00
}
void EventStream::runStream() {
2016-04-29 21:11:14 +08:00
Event::Initialise();
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
openComms();
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
checkInitialised();
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
updateFrameRate( (double)event_data->frame_count/event_data->duration );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if ( type == STREAM_JPEG )
fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );
2013-03-17 07:45:21 +08:00
if ( !event_data ) {
2016-04-29 21:11:14 +08:00
sendTextFrame( "No event data found" );
exit( 0 );
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
unsigned int delta_us = 0;
while( !zm_terminate ) {
2016-04-29 21:11:14 +08:00
gettimeofday( &now, NULL );
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
while(checkCommandQueue());
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if ( step != 0 )
curr_frame_id += step;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
checkEventLoaded();
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// Get current frame data
FrameData *frame_data = &event_data->frames[curr_frame_id-1];
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
//Info( "cst:%.2f", curr_stream_time );
//Info( "cfid:%d", curr_frame_id );
//Info( "fdt:%d", frame_data->timestamp );
if ( !paused ) {
2016-04-29 21:11:14 +08:00
bool in_event = true;
double time_to_event = 0;
if ( replay_rate > 0 ) {
2016-04-29 21:11:14 +08:00
time_to_event = event_data->frames[0].timestamp - curr_stream_time;
if ( time_to_event > 0 )
in_event = false;
} else if ( replay_rate < 0 ) {
2016-04-29 21:11:14 +08:00
time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp;
if ( time_to_event > 0 )
in_event = false;
}
if ( !in_event ) {
2016-04-29 21:11:14 +08:00
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
if ( actual_delta_time > 1 ) {
2016-04-29 21:11:14 +08:00
static char frame_text[64];
snprintf( frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event );
if ( !sendTextFrame( frame_text ) )
zm_terminate = true;
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
//else
//{
usleep( STREAM_PAUSE_WAIT );
//curr_stream_time += (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000));
curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
//}
continue;
}
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
// Figure out if we should send this frame
bool send_frame = false;
if ( !paused ) {
2016-04-29 21:11:14 +08:00
// If we are streaming and this frame is due to be sent
if ( ((curr_frame_id-1)%frame_mod) == 0 ) {
2016-04-29 21:11:14 +08:00
delta_us = (unsigned int)(frame_data->delta * 1000000);
// if effective > base we should speed up frame delivery
delta_us = (unsigned int)((delta_us * base_fps)/effective_fps);
// but must not exceed maxfps
delta_us = max(delta_us, 1000000 / maxfps);
send_frame = true;
}
} else if ( step != 0 ) {
2016-04-29 21:11:14 +08:00
// We are paused and are just stepping forward or backward one frame
step = 0;
send_frame = true;
} else {
2016-04-29 21:11:14 +08:00
// We are paused, and doing nothing
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
if ( actual_delta_time > MAX_STREAM_DELAY ) {
2016-04-29 21:11:14 +08:00
// Send keepalive
Debug( 2, "Sending keepalive frame" );
send_frame = true;
}
}
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
if ( send_frame )
if ( !sendFrame( delta_us ) )
zm_terminate = true;
2013-03-17 07:45:21 +08:00
2016-04-29 21:11:14 +08:00
curr_stream_time = frame_data->timestamp;
2013-03-17 07:45:21 +08:00
if ( !paused ) {
2016-04-29 21:11:14 +08:00
curr_frame_id += replay_rate>0?1:-1;
if ( send_frame && type != STREAM_MPEG ) {
2016-04-29 21:11:14 +08:00
Debug( 3, "dUs: %d", delta_us );
usleep( delta_us );
}
} else {
2016-04-29 21:11:14 +08:00
usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) );
2013-03-17 07:45:21 +08:00
}
2016-04-29 21:11:14 +08:00
}
2013-03-17 07:45:21 +08:00
#if HAVE_LIBAVCODEC
2016-04-29 21:11:14 +08:00
if ( type == STREAM_MPEG )
delete vid_stream;
2013-03-17 07:45:21 +08:00
#endif // HAVE_LIBAVCODEC
2016-04-29 21:11:14 +08:00
closeComms();
2013-03-17 07:45:21 +08:00
}