zoneminder/src/zm.cpp

2142 lines
61 KiB
C++
Raw Normal View History

//
// ZoneMinder Core Implementation, $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 "zm.h"
MYSQL dbconn;
void Zone::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 )
{
monitor = p_monitor;
id = p_id;
label = new char[strlen(p_label)+1];
strcpy( label, p_label );
type = p_type;
limits = p_limits;
alarm_rgb = p_alarm_rgb;
alarm_threshold = p_alarm_threshold;
min_alarm_pixels = p_min_alarm_pixels;
max_alarm_pixels = p_max_alarm_pixels;
filter_box = p_filter_box;
min_filter_pixels = p_min_filter_pixels;
max_filter_pixels = p_max_filter_pixels;
min_blob_pixels = p_min_blob_pixels;
max_blob_pixels = p_max_blob_pixels;
min_blobs = p_min_blobs;
max_blobs = p_max_blobs;
Info(( "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, AT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d\n", id, label, type, limits.Width(), limits.Height(), alarm_rgb, alarm_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ));
alarmed = false;
alarm_pixels = 0;
alarm_filter_pixels = 0;
alarm_blob_pixels = 0;
alarm_blobs = 0;
image = 0;
score = 0;
}
Zone::~Zone()
{
delete[] label;
delete image;
}
void Zone::RecordStats( const Event *event )
{
static char sql[256];
sprintf( sql, "insert into Stats set MonitorId=%d, ZoneId=%d, EventId=%d, FrameId=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", monitor->Id(), id, event->Id(), event->Frames()+1, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't insert event: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
}
int Zone::Load( Monitor *monitor, Zone **&zones )
{
static char sql[256];
sprintf( sql, "select Id,Name,Type+0,Units,LoX,LoY,HiX,HiY,AlarmRGB,AlarmThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs from Zones where MonitorId = %d order by Type, Id", monitor->Id() );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't run query: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result )
{
Error(( "Can't use query result: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
int n_zones = mysql_num_rows( result );
Info(( "Got %d zones for monitor %s\n", n_zones, monitor->Name() ));
delete[] zones;
zones = new Zone *[n_zones];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
{
int Id = atoi(dbrow[0]);
const char *Name = dbrow[1];
int Type = atoi(dbrow[2]);
const char *Units = dbrow[3];
int LoX = atoi(dbrow[4]);
int LoY = atoi(dbrow[5]);
int HiX = atoi(dbrow[6]);
int HiY = atoi(dbrow[7]);
int AlarmRGB = dbrow[8]?atoi(dbrow[8]):0;
int AlarmThreshold = dbrow[9]?atoi(dbrow[9]):0;
int MinAlarmPixels = dbrow[10]?atoi(dbrow[10]):0;
int MaxAlarmPixels = dbrow[11]?atoi(dbrow[11]):0;
int FilterX = dbrow[12]?atoi(dbrow[12]):0;
int FilterY = dbrow[13]?atoi(dbrow[13]):0;
int MinFilterPixels = dbrow[14]?atoi(dbrow[14]):0;
int MaxFilterPixels = dbrow[15]?atoi(dbrow[15]):0;
int MinBlobPixels = dbrow[16]?atoi(dbrow[16]):0;
int MaxBlobPixels = dbrow[17]?atoi(dbrow[17]):0;
int MinBlobs = dbrow[18]?atoi(dbrow[18]):0;
int MaxBlobs = dbrow[19]?atoi(dbrow[19]):0;
if ( !strcmp( Units, "Percent" ) )
{
LoX = (LoX*(monitor->Width()-1))/100;
LoY = (LoY*(monitor->Height()-1))/100;
HiX = (HiX*(monitor->Width()-1))/100;
HiY = (HiY*(monitor->Height()-1))/100;
MinAlarmPixels = (MinAlarmPixels*monitor->Width()*monitor->Height())/100;
MaxAlarmPixels = (MaxAlarmPixels*monitor->Width()*monitor->Height())/100;
MinFilterPixels = (MinFilterPixels*monitor->Width()*monitor->Height())/100;
MaxFilterPixels = (MaxFilterPixels*monitor->Width()*monitor->Height())/100;
MinBlobPixels = (MinBlobPixels*monitor->Width()*monitor->Height())/100;
MaxBlobPixels = (MaxBlobPixels*monitor->Width()*monitor->Height())/100;
}
if ( atoi(dbrow[2]) == Zone::INACTIVE )
{
zones[i] = new Zone( monitor, Id, Name, Box( LoX, LoY, HiX, HiY ) );
}
else
{
zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, Box( LoX, LoY, HiX, HiY ), AlarmRGB, AlarmThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs );
}
}
if ( mysql_errno( &dbconn ) )
{
Error(( "Can't fetch row: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
// Yadda yadda
mysql_free_result( result );
return( n_zones );
}
void Image::ReadJpeg( const char *filename )
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
FILE * infile;
if ((infile = fopen(filename, "rb" )) == NULL)
{
Error(( "Can't open %s: %s\n", filename, strerror(errno)));
exit(1);
}
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
width = cinfo.image_width;
height = cinfo.image_height;
colours = cinfo.num_components;
size = width*height*colours;
assert( colours == 1 || colours == 3 );
buffer = new JSAMPLE[size];
jpeg_start_decompress(&cinfo);
JSAMPROW row_pointer; /* pointer to a single row */
int row_stride = width * colours; /* physical row width in buffer */
while (cinfo.output_scanline < cinfo.output_height)
{
row_pointer = &buffer[cinfo.output_scanline * row_stride];
jpeg_read_scanlines(&cinfo, &row_pointer, 1);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose( infile );
}
void Image::WriteJpeg( const char *filename ) const
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
FILE *outfile;
if ((outfile = fopen(filename, "wb" )) == NULL)
{
Error(( "Can't open %s: %s\n", filename, strerror(errno)));
exit(1);
}
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = width; /* image width and height, in pixels */
cinfo.image_height = height;
cinfo.input_components = colours; /* # of color components per pixel */
if ( colours == 1 )
{
cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
}
else
{
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
}
jpeg_set_defaults(&cinfo);
cinfo.dct_method = JDCT_FASTEST;
//jpeg_set_quality(&cinfo, 100, false);
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row_pointer; /* pointer to a single row */
int row_stride = cinfo.image_width * cinfo.input_components; /* physical row width in buffer */
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer = &buffer[cinfo.next_scanline * row_stride];
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose( outfile );
}
void Image::EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size ) const
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_mem_dest(&cinfo, outbuffer, outbuffer_size );
cinfo.image_width = width; /* image width and height, in pixels */
cinfo.image_height = height;
cinfo.input_components = colours; /* # of color components per pixel */
if ( colours == 1 )
{
cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
}
else
{
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
}
jpeg_set_defaults(&cinfo);
cinfo.dct_method = JDCT_FASTEST;
//jpeg_set_quality(&cinfo, 100, false);
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row_pointer; /* pointer to a single row */
int row_stride = cinfo.image_width * cinfo.input_components; /* physical row width in buffer */
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer = &buffer[cinfo.next_scanline * row_stride];
jpeg_write_scanlines(&cinfo, &row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
}
void Image::Overlay( const Image &image )
{
//assert( width == image.width && height == image.height && colours == image.colours );
assert( width == image.width && height == image.height );
unsigned char *pdest = buffer;
unsigned char *psrc = image.buffer;
if ( colours == 1 )
{
if ( image.colours == 1 )
{
while( pdest < (buffer+size) )
{
if ( *psrc )
{
*pdest = *psrc;
}
pdest++;
psrc++;
}
}
else
{
Colourise();
pdest = buffer;
while( pdest < (buffer+size) )
{
if ( RED(psrc) || GREEN(psrc) || BLUE(psrc) )
{
RED(pdest) = RED(psrc);
GREEN(pdest) = GREEN(psrc);
BLUE(pdest) = BLUE(psrc);
}
psrc += 3;
pdest += 3;
}
}
}
else
{
if ( image.colours == 1 )
{
while( pdest < (buffer+size) )
{
if ( *psrc )
{
RED(pdest) = GREEN(pdest) = BLUE(pdest) = *psrc++;
}
pdest += 3;
}
}
else
{
while( pdest < (buffer+size) )
{
if ( RED(psrc) || GREEN(psrc) || BLUE(psrc) )
{
RED(pdest) = RED(psrc);
GREEN(pdest) = GREEN(psrc);
BLUE(pdest) = BLUE(psrc);
}
psrc += 3;
pdest += 3;
}
}
}
}
void Image::Blend( const Image &image, double transparency ) const
{
assert( width == image.width && height == image.height && colours == image.colours );
JSAMPLE *psrc = image.buffer;
JSAMPLE *pdest = buffer;
while( pdest < (buffer+size) )
{
*pdest++ = (JSAMPLE)round((*pdest * (1.0-transparency))+(*psrc++ * transparency));
}
}
void Image::Blend( const Image &image, int transparency ) const
{
assert( width == image.width && height == image.height && colours == image.colours );
JSAMPLE *psrc = image.buffer;
JSAMPLE *pdest = buffer;
while( pdest < (buffer+size) )
{
*pdest++ = (JSAMPLE)(((*pdest * (100-transparency))+(*psrc++ * transparency))/100);
}
}
Image *Image::Merge( int n_images, Image *images[] )
{
if ( n_images <= 0 ) return( 0 );
if ( n_images == 1 ) return( new Image( *images[0] ) );
int width = images[0]->width;
int height = images[0]->height;
int colours = images[0]->colours;
for ( int i = 1; i < n_images; i++ )
{
assert( width == images[i]->width && height == images[i]->height && colours == images[i]->colours );
}
Image *result = new Image( width, height, images[0]->colours );
int size = result->size;
for ( int i = 0; i < size; i++ )
{
int total = 0;
JSAMPLE *pdest = result->buffer;
for ( int j = 0; j < n_images; j++ )
{
JSAMPLE *psrc = images[j]->buffer;
total += *psrc;
psrc++;
}
*pdest = total/n_images;
pdest++;
}
return( result );
}
Image *Image::Merge( int n_images, Image *images[], double weight )
{
if ( n_images <= 0 ) return( 0 );
if ( n_images == 1 ) return( new Image( *images[0] ) );
int width = images[0]->width;
int height = images[0]->height;
int colours = images[0]->colours;
for ( int i = 1; i < n_images; i++ )
{
assert( width == images[i]->width && height == images[i]->height && colours == images[i]->colours );
}
Image *result = new Image( *images[0] );
int size = result->size;
double factor = 1.0*weight;
for ( int i = 1; i < n_images; i++ )
{
JSAMPLE *pdest = result->buffer;
JSAMPLE *psrc = images[i]->buffer;
for ( int j = 0; j < size; j++ )
{
*pdest = (JSAMPLE)(((*pdest)*(1.0-factor))+((*psrc)*factor));
pdest++;
psrc++;
}
factor *= weight;
}
return( result );
}
Image *Image::Highlight( int n_images, Image *images[], const Rgb threshold, const Rgb ref_colour )
{
if ( n_images <= 0 ) return( 0 );
if ( n_images == 1 ) return( new Image( *images[0] ) );
int width = images[0]->width;
int height = images[0]->height;
int colours = images[0]->colours;
for ( int i = 1; i < n_images; i++ )
{
assert( width == images[i]->width && height == images[i]->height && colours == images[i]->colours );
}
const Image *reference = Merge( n_images, images );
Image *result = new Image( width, height, images[0]->colours );
int size = result->size;
for ( int c = 0; c < 3; c++ )
{
for ( int i = 0; i < size; i++ )
{
int count = 0;
JSAMPLE *pdest = result->buffer+c;
for ( int j = 0; j < n_images; j++ )
{
JSAMPLE *psrc = images[j]->buffer+c;
if ( abs((*psrc)-RGB_VAL(ref_colour,c)) >= RGB_VAL(threshold,c) )
{
count++;
}
psrc += 3;
}
*pdest = (count*255)/n_images;
pdest += 3;
}
}
return( result );
}
Image *Image::Delta( const Image &image, bool absolute ) const
{
assert( width == image.width && height == image.height && colours == image.colours );
Image *result = new Image( width, height, 1 );
typedef JSAMPLE IMAGE[width][height][colours];
IMAGE &data = reinterpret_cast<IMAGE &>(*buffer);
IMAGE &image_data = reinterpret_cast<IMAGE &>(*image.buffer);
IMAGE &diff_data = reinterpret_cast<IMAGE &>(*result->buffer);
unsigned char *psrc = buffer;
unsigned char *pref = image.buffer;
unsigned char *pdiff = result->buffer;
if ( colours == 1 )
{
if ( absolute )
{
while( psrc < (buffer+size) )
{
*pdiff++ = abs( *psrc++ - *pref++ );
}
}
else
{
while( psrc < (buffer+size) )
{
*pdiff++ = *psrc++ - *pref++;
}
}
}
else
{
if ( absolute )
{
while( psrc < (buffer+size) )
{
int red = abs(*psrc++ - *pref++);
int green = abs(*psrc++ - *pref++);
int blue = abs(*psrc++ - *pref++);
//*pdiff++ = (JSAMPLE)sqrt((red*red + green*green + blue*blue)/3);
*pdiff++ = (JSAMPLE)((red + green + blue)/3);
}
}
else
{
while( psrc < (buffer+size) )
{
int red = *psrc++ - *pref++;
int green = *psrc++ - *pref++;
int blue = *psrc++ - *pref++;
*pdiff++ = 127+((int(red+green+blue))/(3*2));
}
}
}
return( result );
}
bool Image::CheckAlarms( Zone *zone, const Image *delta_image ) const
{
bool alarm = false;
unsigned int score = 0;
zone->ResetStats();
delete zone->image;
Image *diff_image = zone->image = new Image( *delta_image );
int alarm_pixels = 0;
int lo_x = zone->limits.Lo().X();
int lo_y = zone->limits.Lo().Y();
int hi_x = zone->limits.Hi().X();
int hi_y = zone->limits.Hi().Y();
for ( int y = lo_y; y <= hi_y; y++ )
{
unsigned char *pdiff = &diff_image->buffer[(y*diff_image->width)+lo_x];
for ( int x = lo_x; x <= hi_x; x++, pdiff++ )
{
if ( *pdiff > zone->alarm_threshold )
{
*pdiff = WHITE;
alarm_pixels++;
continue;
}
*pdiff = BLACK;
}
}
//diff_image->WriteJpeg( "diff1.jpg" );
if ( !alarm_pixels ) return( false );
if ( zone->min_alarm_pixels && alarm_pixels < zone->min_alarm_pixels ) return( false );
if ( zone->max_alarm_pixels && alarm_pixels > zone->max_alarm_pixels ) return( false );
int filter_pixels = 0;
int bx = zone->filter_box.X();
int by = zone->filter_box.Y();
int bx1 = bx-1;
int by1 = by-1;
// Now eliminate all pixels that don't participate in a blob
for ( int y = lo_y; y <= hi_y; y++ )
{
unsigned char *pdiff = &diff_image->buffer[(y*diff_image->width)+lo_x];
for ( int x = lo_x; x <= hi_x; x++, pdiff++ )
{
if ( *pdiff == WHITE )
{
// Check participation in an X blob
int ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x;
int hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1);
int ldy = (y>=(lo_y+by1))?-by1:lo_y-y;
int hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1);
bool blob = false;
for ( int dy = ldy; !blob && dy <= hdy; dy++ )
{
for ( int dx = ldx; !blob && dx <= hdx; dx++ )
{
blob = true;
for ( int dy2 = 0; blob && dy2 < by; dy2++ )
{
for ( int dx2 = 0; blob && dx2 < bx; dx2++ )
{
unsigned char *cpdiff = &diff_image->buffer[((y+dy+dy2)*diff_image->width)+x+dx+dx2];
if ( !*cpdiff )
{
blob = false;
}
}
}
}
}
if ( !blob )
{
*pdiff = BLACK;
continue;
}
filter_pixels++;
}
}
}
//diff_image->WriteJpeg( "diff2.jpg" );
if ( !filter_pixels ) return( false );
if ( zone->min_filter_pixels && filter_pixels < zone->min_filter_pixels ) return( false );
if ( zone->max_filter_pixels && filter_pixels > zone->max_filter_pixels ) return( false );
int blobs = 0;
typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats;
BlobStats blob_stats[256];
memset( blob_stats, 0, sizeof(BlobStats)*256 );
//printf( "%x\n", diff_image->buffer );
for ( int y = lo_y; y <= hi_y; y++ )
{
unsigned char *pdiff = &diff_image->buffer[(y*diff_image->width)+lo_x];
for ( int x = lo_x; x <= hi_x; x++, pdiff++ )
{
if ( *pdiff == WHITE )
{
//printf( "Got white pixel at %d,%d (%x)\n", x, y, pdiff );
int lx = x>lo_x?*(pdiff-1):0;
int ly = y>lo_y?*(pdiff-diff_image->width):0;
if ( lx )
{
//printf( "Left neighbour is %d\n", lx );
BlobStats *bsx = &blob_stats[lx];
if ( ly )
{
//printf( "Top neighbour is %d\n", ly );
BlobStats *bsy = &blob_stats[ly];
if ( lx == ly )
{
//printf( "Matching neighbours, setting to %d\n", lx );
// Add to the blob from the x side (either side really)
*pdiff = lx;
bsx->count++;
//if ( x < bsx->lo_x ) bsx->lo_x = x;
//if ( y < bsx->lo_y ) bsx->lo_y = y;
if ( x > bsx->hi_x ) bsx->hi_x = x;
if ( y > bsx->hi_y ) bsx->hi_y = y;
}
else
{
// Amortise blobs
BlobStats *bsm = bsx->count>=bsy->count?bsx:bsy;
BlobStats *bss = bsm==bsx?bsy:bsx;
//printf( "Different neighbours, setting pixels of %d to %d\n", bss->tag, bsm->tag );
// Now change all those pixels to the other setting
for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++ )
{
unsigned char *spdiff = &diff_image->buffer[(sy*diff_image->width)+bss->lo_x];
for ( int sx = bss->lo_x; sx <= bss->hi_x; sx++, spdiff++ )
{
//printf( "Pixel at %d,%d (%x) is %d", sx, sy, spdiff, *spdiff );
if ( *spdiff == bss->tag )
{
//printf( ", setting" );
*spdiff = bsm->tag;
}
//printf( "\n" );
}
}
*pdiff = bsm->tag;
// Merge the slave blob into the master
bsm->count += bss->count+1;
if ( x > bsm->hi_x ) bsm->hi_x = x;
if ( y > bsm->hi_y ) bsm->hi_y = y;
if ( bss->lo_x < bsm->lo_x ) bsm->lo_x = bss->lo_x;
if ( bss->lo_y < bsm->lo_y ) bsm->lo_y = bss->lo_y;
if ( bss->hi_x > bsm->hi_x ) bsm->hi_x = bss->hi_x;
if ( bss->hi_y > bsm->hi_y ) bsm->hi_y = bss->hi_y;
// Clear out the old blob
bss->tag = 0;
bss->count = 0;
bss->lo_x = 0;
bss->lo_y = 0;
bss->hi_x = 0;
bss->hi_y = 0;
blobs--;
}
}
else
{
//printf( "Setting to left neighbour %d\n", lx );
// Add to the blob from the x side
*pdiff = lx;
bsx->count++;
//if ( x < bsx->lo_x ) bsx->lo_x = x;
//if ( y < bsx->lo_y ) bsx->lo_y = y;
if ( x > bsx->hi_x ) bsx->hi_x = x;
if ( y > bsx->hi_y ) bsx->hi_y = y;
}
}
else
{
if ( ly )
{
//printf( "Setting to top neighbour %d\n", ly );
// Add to the blob from the y side
BlobStats *bsy = &blob_stats[ly];
*pdiff = ly;
bsy->count++;
//if ( x < bsy->lo_x ) bsy->lo_x = x;
//if ( y < bsy->lo_y ) bsy->lo_y = y;
if ( x > bsy->hi_x ) bsy->hi_x = x;
if ( y > bsy->hi_y ) bsy->hi_y = y;
}
else
{
// Create a new blob
for ( int i = 1; i < WHITE; i++ )
{
BlobStats *bs = &blob_stats[i];
if ( !bs->count )
{
//printf( "Creating new blob %d\n", i );
*pdiff = i;
bs->tag = i;
bs->count++;
bs->lo_x = bs->hi_x = x;
bs->lo_y = bs->hi_y = y;
blobs++;
break;
}
}
}
}
}
}
}
//diff_image->WriteJpeg( "diff3.jpg" );
if ( !blobs ) return( false );
int blob_pixels = filter_pixels;
int min_blob_size = 0;
int max_blob_size = 0;
// Now eliminate blobs under the alarm_threshold
for ( int i = 1; i < WHITE; i++ )
{
BlobStats *bs = &blob_stats[i];
if ( bs->count && ((zone->min_blob_pixels && bs->count < zone->min_blob_pixels) || (zone->max_blob_pixels && bs->count > zone->max_blob_pixels)) )
{
//Info(( "Eliminating blob %d, %d pixels (%d,%d - %d,%d)\n", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y ));
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ )
{
unsigned char *spdiff = &diff_image->buffer[(sy*diff_image->width)+bs->lo_x];
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ )
{
if ( *spdiff == bs->tag )
{
*spdiff = BLACK;
}
}
}
blobs--;
blob_pixels -= bs->count;
bs->tag = 0;
bs->count = 0;
bs->lo_x = 0;
bs->lo_y = 0;
bs->hi_x = 0;
bs->hi_y = 0;
}
else
{
if ( bs->count )
{
if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count;
if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count;
}
}
}
if ( !blobs ) return( false );
if ( zone->min_blobs && blobs < zone->min_blobs ) return( false );
if ( zone->max_blobs && blobs > zone->max_blobs ) return( false );
int alarm_lo_x = hi_x+1;
int alarm_hi_x = lo_x-1;
int alarm_lo_y = hi_y+1;
int alarm_hi_y = lo_y-1;
for ( int i = 1; i < WHITE; i++ )
{
BlobStats *bs = &blob_stats[i];
if ( bs->count )
{
if ( alarm_lo_x > bs->lo_x ) alarm_lo_x = bs->lo_x;
if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y;
if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x;
if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y;
}
}
zone->alarm_pixels = alarm_pixels;
zone->alarm_filter_pixels = filter_pixels;
zone->alarm_blob_pixels = blob_pixels;
zone->alarm_blobs = blobs;
zone->min_blob_size = min_blob_size;
zone->max_blob_size = max_blob_size;
zone->alarm_box = Box( Coord( alarm_lo_x, alarm_lo_y ), Coord( alarm_hi_x, alarm_hi_y ) );
zone->score = ((100*blob_pixels)/blobs)/(zone->limits.Size().X()*zone->limits.Size().Y());
if ( zone->Type() == Zone::INCLUSIVE )
{
zone->score /= 2;
}
else if ( zone->Type() == Zone::EXCLUSIVE )
{
zone->score *= 2;
}
score = zone->score;
// Now outline the changed region
if ( zone->alarm_blobs )
{
Image *high_image = zone->image = new Image( *diff_image );
high_image->Colourise();
alarm = true;
memset( high_image->buffer, 0, high_image->size );
for ( int y = lo_y; y <= hi_y; y++ )
{
unsigned char *pdiff = &diff_image->buffer[(y*diff_image->width)+lo_x];
unsigned char *phigh = &high_image->buffer[3*((y*high_image->width)+lo_x)];
for ( int x = lo_x; x <= hi_x; x++, pdiff++, phigh += 3 )
{
bool edge = false;
if ( *pdiff )
{
if ( !edge && x > 0 && !*(pdiff-1) ) edge = true;
if ( !edge && x < (diff_image->width-1) && !*(pdiff+1) ) edge = true;
if ( !edge && y > 0 && !*(pdiff-diff_image->width) ) edge = true;
if ( !edge && y < (diff_image->height-1) && !*(pdiff+diff_image->width) ) edge = true;
}
if ( edge )
{
RED(phigh) = RGB_RED_VAL(zone->alarm_rgb);
GREEN(phigh) = RGB_GREEN_VAL(zone->alarm_rgb);
BLUE(phigh) = RGB_BLUE_VAL(zone->alarm_rgb);
}
}
}
delete diff_image;
//high_image->WriteJpeg( "diff4.jpg" );
Info(( "%s: Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d\n", zone->Label(), alarm_pixels, filter_pixels, blob_pixels, blobs, score ));
}
return( true );
}
unsigned int Image::Compare( const Image &image, int n_zones, Zone *zones[] ) const
{
bool alarm = false;
unsigned int score = 0;
if ( n_zones <= 0 ) return( alarm );
const Image *delta_image = Delta( image );
// Blank out all exclusion zones
unsigned char *psrc = buffer;
for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
{
Zone *zone = zones[n_zone];
zone->alarmed = false;
if ( zone->Type() != Zone::INACTIVE )
{
continue;
}
int lo_x = zone->limits.Lo().X();
int lo_y = zone->limits.Lo().Y();
int hi_x = zone->limits.Hi().X();
int hi_y = zone->limits.Hi().Y();
Debug( 3, ( "Zeroing zone %s, from %d,%d -> %d,%d", zone->Label(), lo_x, lo_y, hi_x, hi_y ));
for ( int y = lo_y; y <= hi_y; y++ )
{
unsigned char *pdelta = &delta_image->buffer[(y*delta_image->width)+lo_x];
for ( int x = lo_x; x <= hi_x; x++ )
{
*pdelta++ = BLACK;
}
}
}
// Find all alarm pixels in active zones
for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
{
Zone *zone = zones[n_zone];
if ( zone->Type() != Zone::ACTIVE )
{
continue;
}
Debug( 3, ( "Checking active zone %s", zone->Label() ));
if ( CheckAlarms( zone, delta_image ) )
{
alarm = true;
score += zone->score;
zone->alarmed = true;
Debug( 3, ( "Zone is alarmed, zone score = %d", zone->score ));
}
}
if ( alarm )
{
for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
{
Zone *zone = zones[n_zone];
if ( zone->Type() != Zone::INCLUSIVE )
{
continue;
}
Debug( 3, ( "Checking inclusive zone %s", zone->Label() ));
if ( CheckAlarms( zone, delta_image ) )
{
alarm = true;
score += zone->score;
zone->alarmed = true;
Debug( 3, ( "Zone is alarmed, zone score = %d", zone->score ));
}
}
}
else
{
// Find all alarm pixels in exclusion zones
for ( int n_zone = 0; n_zone < n_zones; n_zone++ )
{
Zone *zone = zones[n_zone];
if ( zone->Type() != Zone::EXCLUSIVE )
{
continue;
}
Debug( 3, ( "Checking exclusive zone %s", zone->Label() ));
if ( CheckAlarms( zone, delta_image ) )
{
alarm = true;
score += zone->score;
zone->alarmed = true;
Debug( 3, ( "Zone is alarmed, zone score = %d", zone->score ));
}
}
}
delete delta_image;
// This is a small and innocent hack to prevent scores of 0 being returned in alarm state
return( score?score:alarm );
}
void Image::Annotate( const char *text, const Coord &coord, const Rgb colour )
{
int len = strlen( text );
int text_x = coord.X();
int text_y = coord.Y();
if ( text_x > width-(len*CHAR_WIDTH) )
{
text_x = width-(len*CHAR_WIDTH);
}
if ( text_y > height-CHAR_HEIGHT )
{
text_y = height-CHAR_HEIGHT;
}
for ( int y = text_y; y < (text_y+CHAR_HEIGHT); y++)
{
JSAMPLE *ptr = &buffer[((y*width)+text_x)*3];
for ( int x = 0; x < len; x++)
{
int f = fontdata[text[x] * CHAR_HEIGHT + (y-text_y)];
for ( int i = CHAR_WIDTH-1; i >= 0; i--)
{
if (f & (CHAR_START << i))
{
RED(ptr) = RGB_VAL(colour,0);
GREEN(ptr) = RGB_VAL(colour,1);
BLUE(ptr) = RGB_VAL(colour,2);
}
ptr += colours;
}
}
}
}
void Image::Annotate( const char *text, const Coord &coord )
{
int len = strlen( text );
int text_x = coord.X();
int text_y = coord.Y();
if ( text_x > width-(len*CHAR_WIDTH) )
{
text_x = width-(len*CHAR_WIDTH);
}
if ( text_y > height-CHAR_HEIGHT )
{
text_y = height-CHAR_HEIGHT;
}
for ( int y = text_y; y < (text_y+CHAR_HEIGHT); y++)
{
JSAMPLE *ptr = &buffer[((y*width)+text_x)*colours];
for ( int x = 0; x < len; x++)
{
int f = fontdata[text[x] * CHAR_HEIGHT + (y-text_y)];
for ( int i = CHAR_WIDTH-1; i >= 0; i--)
{
if (f & (CHAR_START << i))
{
if ( colours == 1 )
{
*ptr++ = WHITE;
continue;
}
else
{
RED(ptr) = GREEN(ptr) = BLUE(ptr) = WHITE;
ptr += 3;
continue;
}
}
else
{
if ( colours == 1 )
{
*ptr++ = BLACK;
continue;
}
else
{
RED(ptr) = GREEN(ptr) = BLUE(ptr) = BLACK;
ptr += 3;
continue;
}
}
//ptr += colours;
}
}
}
}
void Image::Timestamp( const char *label, time_t when, const Coord &coord )
{
char time_text[64];
strftime( time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime( &when ) );
char text[64];
if ( label )
{
sprintf( text, "%s - %s", label, time_text );
Annotate( text, coord );
}
else
{
Annotate( time_text, coord );
}
}
void Image::Colourise()
{
if ( colours == 1 )
{
colours = 3;
size = width * height * 3;
JSAMPLE *new_buffer = new JSAMPLE[size];
JSAMPLE *psrc = buffer;
JSAMPLE *pdest = new_buffer;
while( pdest < (new_buffer+size) )
{
RED(pdest) = GREEN(pdest) = BLUE(pdest) = *psrc++;
pdest += 3;
}
delete[] buffer;
buffer = new_buffer;
}
}
void Image::DeColourise()
{
if ( colours == 3 )
{
colours = 1;
size = width * height;
JSAMPLE *psrc = buffer;
JSAMPLE *pdest = buffer;
while( pdest < (buffer+size) )
{
*pdest++ = (JSAMPLE)sqrt((RED(psrc) + GREEN(psrc) + BLUE(psrc))/3);
psrc += 3;
}
}
}
Camera::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 ) : id( p_id ), device( p_device ), channel( p_channel ), format( p_format ), width( p_width), height( p_height ), colours( p_colours ), capture( p_capture )
{
name = new char[strlen(p_name)+1];
strcpy( name, p_name );
if ( !camera_count++ && capture )
{
Initialise( device, channel, format, width, height, colours );
}
}
Camera::~Camera()
{
if ( !--camera_count && capture )
{
Terminate();
}
}
bool Camera::GetCurrentSettings( int device, char *output, bool verbose )
{
char device_path[64];
output[0] = 0;
sprintf( device_path, "/dev/video%d", device );
if ( verbose )
sprintf( output, output+strlen(output), "Checking Video Device: %s\n", device_path );
if( (m_videohandle=open(device_path, O_RDONLY)) <=0 )
{
Error(( "Failed to open video device %s: %s\n", device_path, strerror(errno) ));
if ( verbose )
sprintf( output+strlen(output), "Error, failed to open video device: %s\n", strerror(errno) );
else
sprintf( output+strlen(output), "error%d\n", errno );
return( false );
}
struct video_capability vid_cap;
if( !ioctl( m_videohandle, VIDIOCGCAP, &vid_cap))
{
if ( verbose )
{
sprintf( output+strlen(output), "Video Capabilities\n" );
sprintf( output+strlen(output), " Name: %s\n", vid_cap.name );
sprintf( output+strlen(output), " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.type,
vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"",
vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"",
vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"",
vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"",
vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"",
vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"",
vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"",
vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"",
vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"",
vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"",
vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"",
vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"",
vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"",
vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":""
);
sprintf( output+strlen(output), " Video Channels: %d\n", vid_cap.channels );
sprintf( output+strlen(output), " Audio Channels: %d\n", vid_cap.audios );
sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxwidth );
sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxheight );
sprintf( output+strlen(output), " Minimum Width: %d\n", vid_cap.minwidth );
sprintf( output+strlen(output), " Minimum Width: %d\n", vid_cap.minheight );
}
else
{
sprintf( output+strlen(output), "N:%s,", vid_cap.name );
sprintf( output+strlen(output), "T:%d,", vid_cap.type );
sprintf( output+strlen(output), "nC:%d,", vid_cap.channels );
sprintf( output+strlen(output), "nA:%d,", vid_cap.audios );
sprintf( output+strlen(output), "mxW:%d,", vid_cap.maxwidth );
sprintf( output+strlen(output), "mxH:%d,", vid_cap.maxheight );
sprintf( output+strlen(output), "mnW:%d,", vid_cap.minwidth );
sprintf( output+strlen(output), "mnH:%d,", vid_cap.minheight );
}
}
else
{
Error(( "Failed to get video capabilities: %s", strerror(errno) ));
if ( verbose )
sprintf( output, "Error, failed to get video capabilities: %s\n", strerror(errno) );
else
sprintf( output, "error%d\n", errno );
return( false );
}
struct video_window vid_win;
if( !ioctl( m_videohandle, VIDIOCGWIN, &vid_win))
{
if ( verbose )
{
sprintf( output+strlen(output), "Window Attributes\n" );
sprintf( output+strlen(output), " X Offset: %d\n", vid_win.x );
sprintf( output+strlen(output), " Y Offset: %d\n", vid_win.y );
sprintf( output+strlen(output), " Width: %d\n", vid_win.width );
sprintf( output+strlen(output), " Height: %d\n", vid_win.height );
}
else
{
sprintf( output+strlen(output), "X:%d,", vid_win.x );
sprintf( output+strlen(output), "Y:%d,", vid_win.y );
sprintf( output+strlen(output), "W:%d,", vid_win.width );
sprintf( output+strlen(output), "H:%d,", vid_win.height );
}
}
else
{
Error(( "Failed to get window attributes: %s", strerror(errno) ));
if ( verbose )
sprintf( output, "Error, failed to get window attributes: %s\n", strerror(errno) );
else
sprintf( output, "error%d\n", errno );
return( false );
}
struct video_picture vid_pic;
if( !ioctl( m_videohandle, VIDIOCGPICT, &vid_pic))
{
if ( verbose )
{
sprintf( output+strlen(output), "Picture Atributes\n" );
sprintf( output+strlen(output), " Palette: %d - %s\n", vid_pic.palette,
vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":(
vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":(
vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":(
vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":(
vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":(
vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":(
vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":(
vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":(
vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":(
vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":(
vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":(
vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":(
vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":(
vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":(
vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":(
vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown"
))))))))))))))));
sprintf( output+strlen(output), " Colour Depth: %d\n", vid_pic.depth );
sprintf( output+strlen(output), " Brightness: %d\n", vid_pic.brightness );
sprintf( output+strlen(output), " Hue: %d\n", vid_pic.hue );
sprintf( output+strlen(output), " Colour :%d\n", vid_pic.colour );
sprintf( output+strlen(output), " Contrast: %d\n", vid_pic.contrast );
sprintf( output+strlen(output), " Whiteness: %d\n", vid_pic.whiteness );
}
else
{
sprintf( output+strlen(output), "P:%d,", vid_pic.palette );
sprintf( output+strlen(output), "D:%d,", vid_pic.depth );
sprintf( output+strlen(output), "B:%d,", vid_pic.brightness );
sprintf( output+strlen(output), "h:%d,", vid_pic.hue );
sprintf( output+strlen(output), "Cl:%d,", vid_pic.colour );
sprintf( output+strlen(output), "Cn:%d,", vid_pic.contrast );
sprintf( output+strlen(output), "w:%d,", vid_pic.whiteness );
}
}
else
{
Error(( "Failed to get picture attributes: %s", strerror(errno) ));
if ( verbose )
sprintf( output, "Error, failed to get picture attributes: %s\n", strerror(errno) );
else
sprintf( output, "error%d\n", errno );
return( false );
}
for ( int chan = 0; chan < vid_cap.channels; chan++ )
{
struct video_channel vid_src;
vid_src.channel = chan;
if( !ioctl( m_videohandle, VIDIOCGCHAN, &vid_src))
{
if ( verbose )
{
sprintf( output+strlen(output), "Channel %d Attributes\n", chan );
sprintf( output+strlen(output), " Name: %s\n", vid_src.name );
sprintf( output+strlen(output), " Channel: %d\n", vid_src.channel );
sprintf( output+strlen(output), " Flags: %d\n%s%s", vid_src.flags,
vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"",
vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":""
);
sprintf( output+strlen(output), " Type: %d - %s\n", vid_src.type,
vid_src.type==VIDEO_TYPE_TV?"TV":(
vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown"
));
sprintf( output+strlen(output), " Format: %d - %s\n", vid_src.norm,
vid_src.norm==VIDEO_MODE_PAL?"PAL":(
vid_src.norm==VIDEO_MODE_NTSC?"NTSC":(
vid_src.norm==VIDEO_MODE_SECAM?"SECAM":(
vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown"
))));
}
else
{
sprintf( output+strlen(output), "n%d:%d,", chan, vid_src.name );
sprintf( output+strlen(output), "C%d:%d,", chan, vid_src.channel );
sprintf( output+strlen(output), "Fl%d:%x,", chan, vid_src.flags );
sprintf( output+strlen(output), "T%d:%d", chan, vid_src.type );
sprintf( output+strlen(output), "F%d:%d%s,", chan, vid_src.norm, chan==(vid_cap.channels-1)?"":"," );
}
}
else
{
Error(( "Failed to get channel %d attributes: %s\n", chan, strerror(errno) ));
if ( verbose )
sprintf( output, "Error, failed to get channel %d attributes: %s\n", chan, strerror(errno) );
else
sprintf( output, "error%d\n", errno );
return( false );
}
}
return( true );
}
void Camera::Initialise( int device, int channel, int format, int width, int height, int colours )
{
char device_path[64];
sprintf( device_path, "/dev/video%d", device );
if( (m_videohandle=open(device_path, O_RDONLY)) <=0 )
{
Error(( "Failed to open video device %s: %s\n", device_path, strerror(errno) ));
exit(-1);
}
struct video_window vid_win;
if( !ioctl( m_videohandle, VIDIOCGWIN, &vid_win))
{
Info(( "X:%d\n", vid_win.x ));
Info(( "Y:%d\n", vid_win.y ));
Info(( "W:%d\n", vid_win.width ));
Info(( "H:%d\n", vid_win.height ));
}
else
{
Error(( "Failed to get window attributes: %s\n", strerror(errno) ));
exit(-1);
}
vid_win.x = 0;
vid_win.y = 0;
vid_win.width = width;
vid_win.height = height;
if( ioctl( m_videohandle, VIDIOCSWIN, &vid_win ) )
{
Error(( "Failed to set window attributes: %s\n", strerror(errno) ));
if ( !ZM_STRICT_VIDEO_CONFIG ) exit(-1);
}
struct video_picture vid_pic;
if( !ioctl( m_videohandle, VIDIOCGPICT, &vid_pic))
{
Info(( "P:%d\n", vid_pic.palette ));
Info(( "D:%d\n", vid_pic.depth ));
Info(( "B:%d\n", vid_pic.brightness ));
Info(( "h:%d\n", vid_pic.hue ));
Info(( "Cl:%d\n", vid_pic.colour ));
Info(( "Cn:%d\n", vid_pic.contrast ));
}
else
{
Error(( "Failed to get picture attributes: %s\n", strerror(errno) ));
exit(-1);
}
if ( colours == 1 )
{
vid_pic.palette = VIDEO_PALETTE_GREY;
vid_pic.depth = 8;
}
else
{
vid_pic.palette = VIDEO_PALETTE_RGB24;
vid_pic.depth = 24;
}
if( ioctl( m_videohandle, VIDIOCSPICT, &vid_pic ) )
{
Error(( "Failed to set picture attributes: %s\n", strerror(errno) ));
if ( !ZM_STRICT_VIDEO_CONFIG ) exit(-1);
}
if(!ioctl(m_videohandle, VIDIOCGMBUF, &m_vmb))
{
m_vmm = new video_mmap[m_vmb.frames];
Info(( "vmb.frames = %d\n", m_vmb.frames ));
Info(( "vmb.size = %d\n", m_vmb.size ));
}
else
{
Error(( "Failed to setup memory: %s\n", strerror(errno) ));
exit(-1);
}
for(int loop=0; loop < m_vmb.frames; loop++)
{
m_vmm[loop].frame = loop;
m_vmm[loop].width = width;
m_vmm[loop].height = height;
m_vmm[loop].format = (colours==1?VIDEO_PALETTE_GREY:VIDEO_PALETTE_RGB24);
}
m_buffer = (unsigned char *)mmap(0, m_vmb.size, PROT_READ, MAP_SHARED, m_videohandle,0);
if( !((long)m_buffer > 0) )
{
Error(( "Could not mmap video: %s", strerror(errno) ));
exit(-1);
}
struct video_channel vid_src;
vid_src.channel = channel;
if( !ioctl( m_videohandle, VIDIOCGCHAN, &vid_src))
{
Info(( "C:%d\n", vid_src.channel ));
Info(( "F:%d\n", vid_src.norm ));
Info(( "Fl:%x\n", vid_src.flags ));
Info(( "T:%d\n", vid_src.type ));
}
else
{
Error(( "Failed to get camera source: %s\n", strerror(errno) ));
exit(-1);
}
//vid_src.norm = VIDEO_MODE_AUTO;
vid_src.norm = format;
vid_src.flags = 0;
vid_src.type = VIDEO_TYPE_CAMERA;
if(ioctl(m_videohandle, VIDIOCSCHAN, &vid_src))
{
Error(( "Failed to set camera source %d: %s\n", channel, strerror(errno) ));
if ( !ZM_STRICT_VIDEO_CONFIG ) exit(-1);
}
if( !ioctl( m_videohandle, VIDIOCGWIN, &vid_win))
{
Info(( "X:%d\n", vid_win.x ));
Info(( "Y:%d\n", vid_win.y ));
Info(( "W:%d\n", vid_win.width ));
Info(( "H:%d\n", vid_win.height ));
}
else
{
Error(( "Failed to get window data: %s\n", strerror(errno) ));
exit(-1);
}
if( !ioctl( m_videohandle, VIDIOCGPICT, &vid_pic))
{
Info(( "P:%d\n", vid_pic.palette ));
Info(( "D:%d\n", vid_pic.depth ));
Info(( "B:%d\n", vid_pic.brightness ));
Info(( "h:%d\n", vid_pic.hue ));
Info(( "Cl:%d\n", vid_pic.colour ));
Info(( "Cn:%d\n", vid_pic.contrast ));
}
else
{
Error(( "Failed to get window data: %s\n", strerror(errno) ));
exit(-1);
}
}
void Camera::Terminate()
{
munmap((char*)m_buffer, m_vmb.size);
delete[] m_vmm;
close(m_videohandle);
}
int Camera::m_cap_frame = 0;
int Camera::m_sync_frame = 0;
video_mbuf Camera::m_vmb;
video_mmap *Camera::m_vmm;
int Camera::m_videohandle;
unsigned char *Camera::m_buffer=0;
int Camera::camera_count = 0;
Event::Event( Monitor *p_monitor, time_t p_start_time ) : monitor( p_monitor ), start_time( p_start_time )
{
static char sql[256];
static char start_time_str[32];
strftime( start_time_str, sizeof(start_time_str), "%Y-%m-%d %H:%M:%S", localtime( &start_time ) );
sprintf( sql, "insert into Events set MonitorId=%d, Name='Event', StartTime='%s'", monitor->Id(), start_time_str );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't insert event: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
id = mysql_insert_id( &dbconn );
start_frame_id = 0;
end_frame_id = 0;
end_time = 0;
frames = 0;
alarm_frames = 0;
tot_score = 0;
max_score = 0;
sprintf( path, ZM_DIR_EVENTS "/%s/%04d", monitor->Name(), id );
struct stat statbuf;
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR )
{
if ( mkdir( path, 0755 ) )
{
Error(( "Can't make %s: %s\n", path, strerror(errno)));
}
}
}
Event::~Event()
{
static char sql[256];
static char end_time_str[32];
strftime( end_time_str, sizeof(end_time_str), "%Y-%m-%d %H:%M:%S", localtime( &end_time ) );
sprintf( sql, "update Events set Name='Event-%d', EndTime = '%s', Length = %d, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", id, end_time_str, (end_time-start_time), frames, alarm_frames, tot_score, (int)(tot_score/alarm_frames), max_score, id );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't update event: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
}
void Event::AddFrame( time_t timestamp, const Image *image, const Image *alarm_image, unsigned int score )
{
frames++;
static char event_file[PATH_MAX];
sprintf( event_file, "%s/capture-%03d.jpg", path, frames );
image->WriteJpeg( event_file );
static char sql[256];
sprintf( sql, "insert into Frames set EventId=%d, FrameId=%d, AlarmFrame=%d, ImagePath='%s', TimeStamp=from_unixtime(%d), Score=%d", id, frames, alarm_image!=0, event_file, timestamp, score );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't insert frame: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
end_frame_id = mysql_insert_id( &dbconn );
if ( !start_frame_id ) start_frame_id = end_frame_id;
end_time = timestamp;
if ( !start_time ) start_time = end_time;
if ( alarm_image )
{
alarm_frames++;
sprintf( event_file, "%s/analyse-%03d.jpg", path, frames );
alarm_image->WriteJpeg( event_file );
tot_score += score;
if ( score > max_score )
max_score = score;
}
}
void Event::StreamEvent( const char *path, int event_id, unsigned long refresh, FILE *fd )
{
static char sql[256];
sprintf( sql, "select Id, EventId, ImagePath, TimeStamp from Frames where EventId = %d order by Id", event_id );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't run query: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result )
{
Error(( "Can't use query result: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
fprintf( fd, "Server: ZoneMinder Stream Server\r\n" );
fprintf( fd, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n" );
fprintf( fd, "\r\n" );
fprintf( fd, "--ZoneMinderFrame\n" );
int n_frames = mysql_num_rows( result );
Info(( "Got %d frames\n", n_frames ));
FILE *fdj = NULL;
int n_bytes = 0;
static unsigned char buffer[0x10000];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
{
char filepath[PATH_MAX];
sprintf( filepath, "%s/%s", path, dbrow[2] );
if ( fdj = fopen( filepath, "r" ) )
{
fprintf( fd, "Content-type: image/jpg\n\n" );
while ( n_bytes = fread( buffer, 1, sizeof(buffer), fdj ) )
{
fwrite( buffer, 1, n_bytes, fd );
}
fprintf( fd, "\n--ZoneMinderFrame\n" );
fflush( fd );
fclose( fdj );
}
else
{
Error(( "Can't open %s: %s", filepath, strerror(errno) ));
}
usleep( refresh*1000 );
}
if ( mysql_errno( &dbconn ) )
{
Error(( "Can't fetch row: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
// Yadda yadda
mysql_free_result( result );
}
Monitor::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_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_alarm_frame_count, int p_fps_report_interval, int p_ref_blend_perc, int p_n_zones, Zone *p_zones[] ) : Camera( p_id, p_name, p_device, p_channel, p_format, p_width, p_height, p_colours, p_capture ), function( (Function)p_function ), image( p_width, p_height, p_colours ), ref_image( p_width, p_height, p_colours ), label_coord( p_label_coord ), image_buffer_count( p_image_buffer_count ), warmup_count( p_warmup_count ), pre_event_count( p_pre_event_count ), post_event_count( p_post_event_count ), alarm_frame_count( p_alarm_frame_count ), fps_report_interval( p_fps_report_interval ), ref_blend_perc( p_ref_blend_perc ), n_zones( p_n_zones ), zones( p_zones )
{
strcpy( label_format, p_label_format );
fps = 0.0;
event_count = 0;
image_count = 0;
first_alarm_count = 0;
last_alarm_count = 0;
state = IDLE;
int shared_images_size = sizeof(SharedImages)+(image_buffer_count*sizeof(time_t))+(image_buffer_count*colours*width*height);
shmid = shmget( ZM_SHM_KEY|id, shared_images_size, IPC_CREAT|0777 );
if ( shmid < 0 )
{
Error(( "Can't shmget: %s\n", strerror(errno)));
exit( -1 );
}
unsigned char *shm_ptr = (unsigned char *)shmat( shmid, 0, 0 );
shared_images = (SharedImages *)shm_ptr;
if ( shared_images < 0 )
{
Error(( "Can't shmat: %s\n", strerror(errno)));
exit( -1 );
}
if ( capture )
{
memset( shared_images, 0, shared_images_size );
shared_images->state = IDLE;
shared_images->last_write_index = image_buffer_count;
shared_images->last_read_index = image_buffer_count;
shared_images->last_event = 0;
shared_images->forced_alarm = false;
}
shared_images->timestamps = (time_t *)(shm_ptr+sizeof(SharedImages));
shared_images->images = (unsigned char *)(shm_ptr+sizeof(SharedImages)+(image_buffer_count*sizeof(time_t)));
image_buffer = new Snapshot[image_buffer_count];
for ( int i = 0; i < image_buffer_count; i++ )
{
image_buffer[i].timestamp = &(shared_images->timestamps[i]);
image_buffer[i].image = new Image( width, height, colours, &(shared_images->images[i*colours*width*height]) );
//Info(( "%d: %x - %x", i, image_buffer[i].image, image_buffer[i].image->buffer ));
//*(image_buffer[i].timestamp) = time( 0 );
//image_buffer[i].image = new Image( width, height, colours );
//delete[] image_buffer[i].image->buffer;
//image_buffer[i].image->buffer = &(shared_images->images[i*colours*width*height]);
}
if ( !n_zones )
{
n_zones = 1;
zones = new Zone *[1];
zones[0] = new Zone( this, 0, "All", Zone::ACTIVE, Box( width, height ), RGB_RED );
}
start_time = last_fps_time = time( 0 );
event = 0;
Info(( "Monitor %s has function %d\n", name, function ));
Info(( "Monitor %s LBF = '%s', LBX = %d, LBY = %d\n", name, label_format, label_coord.X(), label_coord.Y() ));
Info(( "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, FRI = %d, RBP = %d\n", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, fps_report_interval, ref_blend_perc ));
if ( !capture )
{
ref_image.Assign( width, height, colours, image_buffer[shared_images->last_write_index].image->buffer );
}
else
{
static char path[PATH_MAX];
sprintf( path, ZM_DIR_EVENTS );
struct stat statbuf;
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR )
{
if ( mkdir( path, 0755 ) )
{
Error(( "Can't make %s: %s\n", path, strerror(errno)));
}
}
sprintf( path, ZM_DIR_EVENTS "/%s", name );
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR )
{
if ( mkdir( path, 0755 ) )
{
Error(( "Can't make %s: %s\n", path, strerror(errno)));
}
}
}
record_event_stats = ZM_RECORD_EVENT_STATS;
}
Monitor::~Monitor()
{
delete[] image_buffer;
struct shmid_ds shm_data;
if ( shmctl( shmid, IPC_STAT, &shm_data ) )
{
Error(( "Can't shmctl: %s\n", strerror(errno)));
exit( -1 );
}
if ( shm_data.shm_nattch <= 1 )
{
if ( shmctl( shmid, IPC_RMID, 0 ) )
{
Error(( "Can't shmctl: %s\n", strerror(errno)));
exit( -1 );
}
}
}
void Monitor::AddZones( int p_n_zones, Zone *p_zones[] )
{
n_zones = p_n_zones;
zones = p_zones;
}
Monitor::State Monitor::GetState() const
{
return( shared_images->state );
}
int Monitor::GetImage( int index ) const
{
if ( index < 0 || index > image_buffer_count )
{
index = shared_images->last_write_index;
}
Snapshot *snap = &image_buffer[index];
Image *image = snap->image;
char filename[64];
sprintf( filename, "%s.jpg", name );
image->WriteJpeg( filename );
return( 0 );
}
time_t Monitor::GetTimestamp( int index ) const
{
if ( index < 0 || index > image_buffer_count )
{
index = shared_images->last_write_index;
}
Snapshot *snap = &image_buffer[index];
return( *(snap->timestamp) );
}
unsigned int Monitor::GetLastReadIndex() const
{
return( shared_images->last_read_index );
}
unsigned int Monitor::GetLastWriteIndex() const
{
return( shared_images->last_write_index );
}
unsigned int Monitor::GetLastEvent() const
{
return( shared_images->last_event );
}
double Monitor::GetFPS() const
{
int index1 = shared_images->last_write_index;
int index2 = (index1+1)%image_buffer_count;
//Snapshot *snap1 = &image_buffer[index1];
//time_t time1 = *(snap1->timestamp);
time_t time1 = time( 0 );
Snapshot *snap2 = &image_buffer[index2];
time_t time2 = *(snap2->timestamp);
double fps = double(image_buffer_count)/(time1-time2);
return( fps );
}
void Monitor::ForceAlarm()
{
shared_images->forced_alarm = true;
}
void Monitor::CancelAlarm()
{
shared_images->forced_alarm = false;
}
void Monitor::DumpZoneImage()
{
int index = shared_images->last_write_index;
Snapshot *snap = &image_buffer[index];
Image *image = snap->image;
Image zone_image( *image );
zone_image.Colourise();
for( int i = 0; i < n_zones; i++ )
{
unsigned char *psrc = zone_image.buffer;
int lo_x = zones[i]->Limits().Lo().X();
int lo_y = zones[i]->Limits().Lo().Y();
int hi_x = zones[i]->Limits().Hi().X();
int hi_y = zones[i]->Limits().Hi().Y();
for ( int y = 0; y < zone_image.height; y++ )
{
for ( int x = 0; x < zone_image.width; x++, psrc += 3 )
{
if ( ( (x == lo_x || x == hi_x) && (y >= lo_y && y <= hi_y) )
|| ( (y == lo_y || y == hi_y) && (x >= lo_x && x <= hi_x) )
|| ( (x > lo_x && x < hi_x && y > lo_y && y < hi_y) && !(x%2) && !(y%2) ) )
{
if ( zones[i]->Type() == Zone::ACTIVE )
{
RED(psrc) = RGB_RED_VAL(RGB_RED);
GREEN(psrc) = RGB_GREEN_VAL(RGB_RED);
BLUE(psrc) = RGB_BLUE_VAL(RGB_RED);
}
else if ( zones[i]->Type() == Zone::INCLUSIVE )
{
RED(psrc) = RGB_RED_VAL(RGB_GREEN);
GREEN(psrc) = RGB_GREEN_VAL(RGB_GREEN);
BLUE(psrc) = RGB_BLUE_VAL(RGB_GREEN);
}
else if ( zones[i]->Type() == Zone::EXCLUSIVE )
{
RED(psrc) = RGB_RED_VAL(RGB_BLUE);
GREEN(psrc) = RGB_GREEN_VAL(RGB_BLUE);
BLUE(psrc) = RGB_BLUE_VAL(RGB_BLUE);
}
else
{
RED(psrc) = RGB_RED_VAL(RGB_WHITE);
GREEN(psrc) = RGB_GREEN_VAL(RGB_WHITE);
BLUE(psrc) = RGB_BLUE_VAL(RGB_WHITE);
}
}
}
}
}
char filename[64];
sprintf( filename, "%s-Zones.jpg", name );
zone_image.WriteJpeg( filename );
}
void Monitor::DumpImage( Image *image ) const
{
if ( image_count && !(image_count%10) )
{
static char new_filename[64];
static char filename[64];
//sprintf( filename, "%s%04d.jpg", name, image_count );
sprintf( filename, "%s.jpg", name );
sprintf( new_filename, "%s-new.jpg", name );
image->WriteJpeg( new_filename );
rename( new_filename, filename );
}
}
bool Monitor::Analyse()
{
if ( shared_images->last_read_index == shared_images->last_write_index )
{
return( false );
}
time_t now = time( 0 );
if ( image_count && !(image_count%fps_report_interval) )
{
fps = double(fps_report_interval)/(now-last_fps_time);
Info(( "%s: %d - Processing at %.2f fps\n", name, image_count, fps ));
last_fps_time = now;
}
int index = shared_images->last_write_index%image_buffer_count;
Snapshot *snap = &image_buffer[index];
time_t timestamp = *(snap->timestamp);
Image *image = snap->image;
unsigned int score = 0;
if ( Ready() )
{
score = ref_image.Compare( *image, n_zones, zones );
if ( shared_images->forced_alarm )
score = ZM_FORCED_ALARM_SCORE;
if ( score )
{
if ( state == IDLE )
{
event = new Event( this, timestamp );
Info(( "%s: %03d - Gone into alarm state\n", name, image_count ));
int pre_index = ((index+image_buffer_count)-pre_event_count)%image_buffer_count;
for ( int i = 0; i < pre_event_count; i++ )
{
event->AddFrame( *(image_buffer[pre_index].timestamp), image_buffer[pre_index].image );
pre_index = (pre_index+1)%image_buffer_count;
}
//event->AddFrame( now, &image );
}
shared_images->state = state = ALARM;
last_alarm_count = image_count;
}
else
{
if ( state == ALARM )
{
shared_images->state = state = ALERT;
}
else if ( state == ALERT )
{
if ( image_count-last_alarm_count > post_event_count )
{
Info(( "%s: %03d - Left alarm state (%d) - %d(%d) images\n", name, image_count, event->id, event->frames, event->alarm_frames ));
shared_images->last_event = event->id;
delete event;
shared_images->state = state = IDLE;
}
}
}
if ( state != IDLE )
{
if ( state == ALARM )
{
Image alarm_image( *image );
for( int i = 0; i < n_zones; i++ )
{
if ( zones[i]->Alarmed() )
{
alarm_image.Overlay( zones[i]->AlarmImage() );
if ( record_event_stats )
{
zones[i]->RecordStats( event );
}
}
}
event->AddFrame( now, image, &alarm_image, score );
}
else
{
event->AddFrame( now, image );
}
}
}
ref_image.Blend( *image, ref_blend_perc );
//DumpImage( image );
shared_images->last_read_index = index%image_buffer_count;
image_count++;
return( true );
}
void Monitor::ReloadZones()
{
Info(( "Reloading zones for monitor %s\n", name ));
for( int i = 0; i < n_zones; i++ )
{
delete zones[i];
}
//delete[] zones;
n_zones = Zone::Load( this, zones );
DumpZoneImage();
}
int Monitor::Load( int device, Monitor **&monitors, bool capture )
{
static char sql[256];
if ( device == -1 )
{
strcpy( sql, "select Id, Name, Function+0, Device, Channel, Format, Width, Height, Colours, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, FPSReportInterval, RefBlendPerc from Monitors where Function != 'None'" );
}
else
{
sprintf( sql, "select Id, Name, Function+0, Device, Channel, Format, Width, Height, Colours, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, FPSReportInterval, RefBlendPerc from Monitors where Function != 'None' and Device = %d", device );
}
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't run query: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result )
{
Error(( "Can't use query result: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
int n_monitors = mysql_num_rows( result );
Info(( "Got %d monitors\n", n_monitors ));
delete[] monitors;
monitors = new Monitor *[n_monitors];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
{
monitors[i] = new Monitor( atoi(dbrow[0]), dbrow[1], atoi(dbrow[2]), atoi(dbrow[3]), atoi(dbrow[4]), atoi(dbrow[5]), atoi(dbrow[6]), atoi(dbrow[7]), atoi(dbrow[8])/8, capture, dbrow[9], Coord( atoi(dbrow[10]), atoi(dbrow[11]) ), atoi(dbrow[12]), atoi(dbrow[13]), atoi(dbrow[14]), atoi(dbrow[15]), atoi(dbrow[16]), atoi(dbrow[17]), atoi(dbrow[18]) );
Zone **zones = 0;
int n_zones = Zone::Load( monitors[i], zones );
monitors[i]->AddZones( n_zones, zones );
Info(( "Loaded monitor %d(%s), %d zones\n", atoi(dbrow[0]), dbrow[1], n_zones ));
}
if ( mysql_errno( &dbconn ) )
{
Error(( "Can't fetch row: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
// Yadda yadda
mysql_free_result( result );
return( n_monitors );
}
Monitor *Monitor::Load( int id, bool load_zones )
{
static char sql[256];
sprintf( sql, "select Id, Name, Function+0, Device, Channel, Format, Width, Height, Colours, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, FPSReportInterval, RefBlendPerc from Monitors where Id = %d", id );
if ( mysql_query( &dbconn, sql ) )
{
Error(( "Can't run query: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result )
{
Error(( "Can't use query result: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
int n_monitors = mysql_num_rows( result );
Info(( "Got %d monitors\n", n_monitors ));
Monitor *monitor = 0;
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
{
monitor = new Monitor( atoi(dbrow[0]), dbrow[1], atoi(dbrow[2]), atoi(dbrow[3]), atoi(dbrow[4]), atoi(dbrow[5]), atoi(dbrow[6]), atoi(dbrow[7]), atoi(dbrow[8])/8, false, dbrow[9], Coord( atoi(dbrow[10]), atoi(dbrow[11]) ), atoi(dbrow[12]), atoi(dbrow[13]), atoi(dbrow[14]), atoi(dbrow[15]), atoi(dbrow[16]), atoi(dbrow[17]), atoi(dbrow[18]) );
int n_zones = 0;
if ( load_zones )
{
Zone **zones = 0;
n_zones = Zone::Load( monitor, zones );
monitor->AddZones( n_zones, zones );
}
Info(( "Loaded monitor %d(%s), %d zones\n", atoi(dbrow[0]), dbrow[1], n_zones ));
}
if ( mysql_errno( &dbconn ) )
{
Error(( "Can't fetch row: %s\n", mysql_error( &dbconn ) ));
exit( mysql_errno( &dbconn ) );
}
// Yadda yadda
mysql_free_result( result );
return( monitor );
}
void Monitor::StreamImages( unsigned long idle, unsigned long refresh, FILE *fd )
{
fprintf( fd, "Server: ZoneMinder Stream Server\r\n" );
fprintf( fd, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n" );
fprintf( fd, "\r\n" );
fprintf( fd, "--ZoneMinderFrame\n" );
int last_read_index = image_buffer_count;
JOCTET img_buffer[width*height*colours];
int img_buffer_size = 0;
int loop_count = (idle/refresh)-1;
while ( true )
{
if ( last_read_index != shared_images->last_write_index )
{
// Send the next frame
last_read_index = shared_images->last_write_index;
int index = shared_images->last_write_index%image_buffer_count;
//Info(( "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ));
Snapshot *snap = &image_buffer[index];
Image *image = snap->image;
image->EncodeJpeg( img_buffer, &img_buffer_size );
fprintf( fd, "Content-type: image/jpg\n\n" );
fwrite( img_buffer, 1, img_buffer_size, fd );
fprintf( fd, "\n--ZoneMinderFrame\n" );
fflush( fd );
}
usleep( refresh*1000 );
for ( int i = 0; shared_images->state == IDLE && i < loop_count; i++ )
{
usleep( refresh*1000 );
}
}
}