zoneminder/src/zm.h

704 lines
18 KiB
C++

//
// ZoneMinder Core Interfaces, $Date$, $Revision$
// Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/shm.h>
#include <linux/videodev.h>
#include <mysql/mysql.h>
#include "font_6x11.h"
extern "C"
{
#include <jpeglib.h>
double round(double);
void jpeg_mem_dest(j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_size );
}
extern "C"
{
#include "zmcfg.h"
#include "zmdbg.h"
}
typedef unsigned int Rgb;
#define RED(byte) (*(byte))
#define GREEN(byte) (*(byte+1))
#define BLUE(byte) (*(byte+2))
#define WHITE 0xff
#define WHITE_R 0xff
#define WHITE_G 0xff
#define WHITE_B 0xff
#define BLACK 0x00
#define BLACK_R 0x00
#define BLACK_G 0x00
#define BLACK_B 0x00
#define RGB_WHITE (0x00ffffff)
#define RGB_BLACK (0x00000000)
#define RGB_RED (0x00ff0000)
#define RGB_GREEN (0x0000ff00)
#define RGB_BLUE (0x000000ff)
#define RGB_VAL(v,c) (((v)>>(16-((c)*8)))&0xff)
#define RGB_RED_VAL(v) (((v)>>16)&0xff)
#define RGB_GREEN_VAL(v) (((v)>>8)&0xff)
#define RGB_BLUE_VAL(v) ((v)&0xff)
extern MYSQL dbconn;
class Coord
{
private:
int x, y;
public:
inline Coord() : x(0), y(0)
{
}
inline Coord( int p_x, int p_y ) : x(p_x), y(p_y)
{
}
inline Coord( const Coord &p_coord ) : x(p_coord.x), y(p_coord.y)
{
}
inline int &X() { return( x ); }
inline const int &X() const { return( x ); }
inline int &Y() { return( y ); }
inline const int &Y() const { return( y ); }
inline static Coord Range( const Coord &coord1, const Coord &coord2 )
{
Coord result( (coord1.x-coord2.x)+1, (coord1.y-coord2.y)+1 );
return( result );
}
inline bool operator==( const Coord &coord )
{
return( x == coord.x && y == coord.y );
}
inline bool operator!=( const Coord &coord )
{
return( x != coord.x || y != coord.y );
}
inline bool operator>( const Coord &coord )
{
return( x > coord.x && y > coord.y );
}
inline bool operator>=( const Coord &coord )
{
return( !(operator<(coord)) );
}
inline bool operator<( const Coord &coord )
{
return( x < coord.x && y < coord.y );
}
inline bool operator<=( const Coord &coord )
{
return( !(operator>(coord)) );
}
inline Coord &operator+=( const Coord &coord )
{
x += coord.x;
y += coord.y;
return( *this );
}
inline Coord &operator-=( const Coord &coord )
{
x -= coord.x;
y -= coord.y;
return( *this );
}
inline friend Coord operator+( const Coord &coord1, const Coord &coord2 )
{
Coord result( coord1 );
result += coord2;
return( result );
}
inline friend Coord operator-( const Coord &coord1, const Coord &coord2 )
{
Coord result( coord1 );
result -= coord2;
return( result );
}
};
class Box
{
private:
Coord lo, hi;
Coord size;
public:
inline Box()
{
}
inline Box( int p_size ) : lo( 0, 0 ), hi ( p_size-1, p_size-1 ), size( Coord::Range( hi, lo ) )
{
}
inline Box( int p_x_size, int p_y_size ) : lo( 0, 0 ), hi ( p_x_size-1, p_y_size-1 ), size( Coord::Range( hi, lo ) )
{
}
inline Box( int lo_x, int lo_y, int hi_x, int hi_y ) : lo( lo_x, lo_y ), hi( hi_x, hi_y ), size( Coord::Range( hi, lo ) )
{
}
inline Box( const Coord &p_lo, const Coord &p_hi ) : lo( p_lo ), hi( p_hi ), size( Coord::Range( hi, lo ) )
{
}
inline const Coord &Lo() const { return( lo ); }
inline int LoX() const { return( lo.X() ); }
inline int LoY() const { return( lo.Y() ); }
inline const Coord &Hi() const { return( hi ); }
inline int HiX() const { return( hi.X() ); }
inline int HiY() const { return( hi.Y() ); }
inline const Coord &Size() const { return( size ); }
inline int Width() const
{
return( size.X() );
}
inline int Height() const
{
return( size.Y() );
}
inline bool Inside( const Coord &coord ) const
{
return( coord.X() >= lo.X() && coord.X() <= hi.X() && coord.Y() >= lo.Y() && coord.Y() <= hi.Y() );
}
};
class Monitor;
class Event;
class Zone
{
friend class Image;
public:
typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, INACTIVE } ZoneType;
protected:
// Inputs
Monitor *monitor;
int id;
char *label;
ZoneType type;
Box limits;
Rgb alarm_rgb;
int alarm_threshold;
int min_alarm_pixels;
int max_alarm_pixels;
Coord filter_box;
int min_filter_pixels;
int max_filter_pixels;
int min_blob_pixels;
int max_blob_pixels;
int min_blobs;
int max_blobs;
// Outputs/Statistics
bool alarmed;
int alarm_pixels;
int alarm_filter_pixels;
int alarm_blob_pixels;
int alarm_blobs;
int min_blob_size;
int max_blob_size;
Box alarm_box;
unsigned int score;
Image *image;
protected:
void Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Box &p_limits, const Rgb p_alarm_rgb, int p_alarm_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs );
public:
Zone( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Box &p_limits, const Rgb p_alarm_rgb, int p_alarm_threshold=15, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0 )
{
Setup( p_monitor, p_id, p_label, p_type, p_limits, p_alarm_rgb, p_alarm_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs );
}
Zone( Monitor *p_monitor, int p_id, const char *p_label, const Box &p_limits, const Rgb p_alarm_rgb, int p_alarm_threshold=15, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0 )
{
Setup( p_monitor, p_id, p_label, Zone::ACTIVE, p_limits, p_alarm_rgb, p_alarm_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs );
}
Zone( Monitor *p_monitor, int p_id, const char *p_label, const Box &p_limits )
{
Setup( p_monitor, p_id, p_label, Zone::INACTIVE, p_limits, RGB_BLACK, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0 );
}
public:
~Zone();
inline const char *Label() const
{
return( label );
}
inline ZoneType Type() const
{
return( type );
}
inline Image &AlarmImage() const
{
return( *image );
}
inline const Box &Limits() const { return( limits ); }
inline bool Alarmed() const
{
return( alarmed );
}
inline void ResetStats()
{
alarmed = false;
alarm_pixels = 0;
alarm_filter_pixels = 0;
alarm_blob_pixels = 0;
alarm_blobs = 0;
min_blob_size = 0;
max_blob_size = 0;
score = 0;
}
void RecordStats( const Event *event );
static int Load( Monitor *monitor, Zone **&zones );
bool DumpSettings( char *output, bool verbose );
};
class Camera;
class Monitor;
class Image
{
friend class Camera;
friend class Monitor;
protected:
enum { CHAR_HEIGHT=11, CHAR_WIDTH=6, CHAR_START=4 };
protected:
int width;
int height;
int colours;
int size;
JSAMPLE *buffer;
bool our_buffer;
public:
Image( const char *filename )
{
ReadJpeg( filename );
our_buffer = true;
}
Image( int p_width, int p_height, int p_colours, JSAMPLE *p_buffer=0 )
{
width = p_width;
height = p_height;
colours = p_colours;
size = width*height*colours;
if ( !p_buffer )
{
our_buffer = true;
buffer = new JSAMPLE[size];
memset( buffer, 0, size );
}
else
{
our_buffer = false;
buffer = p_buffer;
}
}
Image( const Image &p_image )
{
width = p_image.width;
height = p_image.height;
colours = p_image.colours;
size = p_image.size;
buffer = new JSAMPLE[size];
memcpy( buffer, p_image.buffer, size );
our_buffer = true;
}
~Image()
{
if ( our_buffer )
{
delete[] buffer;
}
}
inline void Assign( int p_width, int p_height, int p_colours, unsigned char *new_buffer )
{
if ( p_width != width || p_height != height || p_colours != colours )
{
width = p_width;
height = p_height;
colours = p_colours;
size = width*height*colours;
delete[] buffer;
buffer = new JSAMPLE[size];
memset( buffer, 0, size );
}
if ( colours == 1 )
{
memcpy( buffer, new_buffer, size );
}
else
{
for ( int i = 0; i < size; i += 3 )
{
buffer[i] = new_buffer[i+2];
buffer[i+1] = new_buffer[i+1];
buffer[i+2] = new_buffer[i];
}
}
}
inline Image &operator=( const unsigned char *new_buffer )
{
memcpy( buffer, new_buffer, size );
return( *this );
}
inline int Width() { return( width ); }
inline int Height() { return( height ); }
void ReadJpeg( const char *filename );
void WriteJpeg( const char *filename ) const;
void EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size ) const;
void Overlay( const Image &image );
void Blend( const Image &image, double transparency=0.1 ) const;
void Blend( const Image &image, int transparency=10 ) const;
static Image *Merge( int n_images, Image *images[] );
static Image *Merge( int n_images, Image *images[], double weight );
static Image *Highlight( int n_images, Image *images[], const Rgb threshold=RGB_BLACK, const Rgb ref_colour=RGB_RED );
Image *Delta( const Image &image, bool absolute=true ) const;
bool CheckAlarms( Zone *zone, const Image *delta_image ) const;
unsigned int Compare( const Image &image, int n_zones, Zone *zones[] ) const;
void Annotate( const char *text, const Coord &coord, const Rgb colour );
void Annotate( const char *text, const Coord &coord );
void Timestamp( const char *label, time_t when, const Coord &coord );
void Colourise();
void DeColourise();
};
class Camera
{
friend class Image;
protected:
int id;
char *name;
int device;
int channel;
int format;
unsigned int width;
unsigned int height;
int colours;
bool capture;
protected:
static int m_cap_frame;
static int m_sync_frame;
static video_mbuf m_vmb;
static video_mmap *m_vmm;
static int m_videohandle;
static unsigned char *m_buffer;
static int camera_count;
public:
Camera( int p_id, char *p_name, int p_device, int p_channel, int p_format, int p_width, int p_height, int p_colours, bool p_capture=true );
~Camera();
inline int Id() const
{
return( id );
}
inline char *Name() const
{
return( name );
}
unsigned int Width() const { return( width ); }
unsigned int Height() const { return( height ); }
static bool Camera::GetCurrentSettings( int device, char *output, bool verbose );
static void Initialise( int device, int channel, int format, int width, int height, int colours );
void Terminate();
inline void PreCapture()
{
//Info(( "%s: Capturing image\n", id ));
if ( camera_count > 1 )
{
//Info(( "Switching\n" ));
struct video_channel vs;
vs.channel = channel;
//vs.norm = VIDEO_MODE_AUTO;
vs.norm = format;
vs.flags = 0;
vs.type = VIDEO_TYPE_CAMERA;
if(ioctl(m_videohandle, VIDIOCSCHAN, &vs))
{
Error(( "Failed to set camera source %d: %s\n", channel, strerror(errno) ));
}
}
//Info(( "MC:%d\n", m_videohandle ));
if ( ioctl(m_videohandle, VIDIOCMCAPTURE, &m_vmm[m_cap_frame]) )
{
Error(( "Capture failure for frame %d: %s\n", m_cap_frame, strerror(errno)));
}
m_cap_frame = (m_cap_frame+1)%m_vmb.frames;
}
inline unsigned char *PostCapture()
{
//Info(( "%s: Capturing image\n", id ));
if ( ioctl(m_videohandle, VIDIOCSYNC, &m_sync_frame) )
{
Error(( "Sync failure for frame %d: %s\n", m_sync_frame, strerror(errno)));
}
unsigned char *buffer = m_buffer+(m_sync_frame*m_vmb.size/m_vmb.frames);
m_sync_frame = (m_sync_frame+1)%m_vmb.frames;
return( buffer );
}
inline void PostCapture( Image &image )
{
//Info(( "%s: Capturing image\n", id ));
if ( ioctl(m_videohandle, VIDIOCSYNC, &m_sync_frame) )
{
Error(( "Sync failure for frame %d: %s\n", m_sync_frame, strerror(errno)));
}
unsigned char *buffer = m_buffer+(m_sync_frame*m_vmb.size/m_vmb.frames);
m_sync_frame = (m_sync_frame+1)%m_vmb.frames;
image.Assign( width, height, colours, buffer );
}
inline unsigned char *Capture()
{
PreCapture();
return( PostCapture() );
}
inline void Capture( Image &image )
{
PreCapture();
return( PostCapture( image ) );
}
};
class Event
{
friend class Monitor;
protected:
int id;
Monitor *monitor;
time_t start_time;
time_t end_time;
int start_frame_id;
int end_frame_id;
int frames;
int alarm_frames;
unsigned int tot_score;
unsigned int max_score;
char path[PATH_MAX];
public:
Event( Monitor *p_monitor, time_t p_start_time );
~Event();
int Id() const { return( id ); }
int Frames() const { return( frames ); }
int AlarmFrames() const { return( alarm_frames ); }
void AddFrame( time_t timestamp, const Image *image, const Image *alarm_frame=NULL, unsigned int score=0 );
static void StreamEvent( const char *path, int event_id, unsigned long refresh=100, FILE *fd=stdout );
};
class Monitor : public Camera
{
public:
typedef enum
{
NONE=1,
PASSIVE,
ACTIVE,
X10
} Function;
typedef enum { IDLE, ALARM, ALERT } State;
protected:
// These are read from the DB and thereafter remain unchanged
char label_format[64]; // The format of the timestamp on the images
Coord label_coord; // The coordinates of the timestamp on the images
int warmup_count; // How many images to process before looking for events
int pre_event_count; // How many images to hold and prepend to an alarm event
int post_event_count; // How many unalarmed images must occur before the alarm state is reset
int alarm_frame_count; // How many alarm frames we must get before we actually do anything about it.
int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count
int fps_report_interval;// How many images should be captured/processed between reporting the current FPS
int ref_blend_perc; // Percentage of new image going into reference image.
Function function;
double fps;
Image image;
Image ref_image;
int event_count;
int image_count;
int first_alarm_count;
int last_alarm_count;
int buffer_count;
State state;
int n_zones;
Zone **zones;
Event *event;
time_t start_time;
time_t last_fps_time;
int shmid;
typedef struct Snapshot
{
time_t *timestamp;
Image *image;
};
Snapshot *image_buffer;
typedef struct
{
State state;
int last_write_index;
int last_read_index;
int last_event;
bool forced_alarm;
time_t *timestamps;
unsigned char *images;
} SharedImages;
SharedImages *shared_images;
bool record_event_stats;
public:
Monitor( int p_id, char *p_name, int p_function, int p_device, int p_channel, int p_format, int p_width, int p_height, int p_colours, bool p_capture, char *p_label_format, const Coord &p_label_coord, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_alarm_frame_count, int p_image_buffer_count, int p_fps_report_interval, int p_ref_blend_perc, int p_n_zones=0, Zone *p_zones[]=0 );
~Monitor();
void AddZones( int p_n_zones, Zone *p_zones[] );
State GetState() const;
int GetImage( int index=-1 ) const;
time_t GetTimestamp( int index=-1 ) const;
unsigned int GetLastReadIndex() const;
unsigned int GetLastWriteIndex() const;
unsigned int GetLastEvent() const;
double GetFPS() const;
void ForceAlarm();
void CancelAlarm();
bool DumpSettings( char *output, bool verbose );
void DumpZoneImage();
inline void Capture()
{
PreCapture();
PostCapture();
}
inline void PostCapture()
{
char label_time_text[64];
char label_text[64];
Camera::PostCapture( image );
time_t now = time( 0 );
if ( label_format[0] )
{
strftime( label_time_text, sizeof(label_time_text), label_format, localtime( &now ) );
sprintf( label_text, label_time_text, name );
image.Annotate( label_text, label_coord );
}
int index = image_count%image_buffer_count;
if ( index == shared_images->last_read_index )
{
Error(( "Buffer overrun at index %d\n", index ));
}
*(image_buffer[index].timestamp) = now;
memcpy( image_buffer[index].image->buffer, image.buffer, image.size );
//Info(( "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ));
shared_images->last_write_index = index;
image_count++;
if ( image_count && !(image_count%fps_report_interval) )
{
fps = double(fps_report_interval)/(now-last_fps_time);
Info(( "%s: %d - Capturing at %.2f fps\n", name, image_count, fps ));
last_fps_time = now;
}
}
inline bool Ready()
{
return( function >= ACTIVE && image_count > warmup_count );
}
void DumpImage( Image *image ) const;
bool Analyse();
void Adjust( double ratio )
{
ref_image.Blend( image, 0.1 );
}
void ReloadZones();
static int Load( int device, Monitor **&monitors, bool capture=true );
static Monitor *Load( int id, bool load_zones=false );
void StreamImages( unsigned long idle=5000, unsigned long refresh=50, FILE *fd=stdout );
};