merge all the commits from the messed up iconnor_video branch

This commit is contained in:
Isaac Connor 2016-04-05 17:14:46 -04:00
parent f73d8be765
commit 9a795432cf
39 changed files with 4487 additions and 4284 deletions

View File

@ -4,7 +4,7 @@
#
cmake_minimum_required (VERSION 2.6)
project (zoneminder)
set(zoneminder_VERSION "1.29.0")
set(zoneminder_VERSION "1.30.0")
# make API version a minor of ZM version
set(zoneminder_API_VERSION "${zoneminder_VERSION}.1")

View File

@ -3,7 +3,7 @@ ZoneMinder H264 Patch
[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png?branch=feature-h264-videostorage)](https://travis-ci.org/ZoneMinder/ZoneMinder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
##Feature-h264-videostorage Branch Details
This branch supports direct recording of h264 cameras into MP4 format uisng the h264 Passthrough option, but only with FFMPEG Monitors currently. It also it provides h264 encoding from jpegs of any other monitor type and local or mpeg cameras. If you encounter any issues, please open an issue on GitHub and attach it to the h264 milestone. But do remember this is bleeding edge so it will have problems.
This branch supports direct recording of h264 cameras into MP4 format uisng the h264 Passthrough option. As well it provides h264 encoding for local or mpeg cameras. If you encounter any issues, please open an issue on GitHub and attach it to the h264 milestone. But do remember this is bleeding edge so it will have problems.
Thanks to @chriswiggins and @mastertheknife for their work, @SteveGilvarry is now maintaining this branch and welcomes any assistance.
**The following SQL changes are required, these will be merged to zmupdate once we are ready to merge this branch to master.**

View File

@ -193,6 +193,7 @@ CREATE TABLE `Events` (
`Length` decimal(10,2) NOT NULL default '0.00',
`Frames` int(10) unsigned default NULL,
`AlarmFrames` int(10) unsigned default NULL,
`DefaultVideo` VARCHAR( 64 ) NOT NULL,
`TotScore` int(10) unsigned NOT NULL default '0',
`AvgScore` smallint(5) unsigned default '0',
`MaxScore` smallint(5) unsigned default '0',
@ -344,6 +345,10 @@ CREATE TABLE `Monitors` (
`Palette` int(10) unsigned NOT NULL default '0',
`Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0',
`Deinterlacing` int(10) unsigned NOT NULL default '0',
`SaveJPEGs` TINYINT NOT NULL DEFAULT '3' ,
`VideoWriter` TINYINT NOT NULL DEFAULT '0',
`EncoderParameters` TEXT NOT NULL,
`RecordAudio` TINYINT NOT NULL DEFAULT '0',
`RTSPDescribe` tinyint(1) unsigned NOT NULL default '0',
`Brightness` mediumint(7) NOT NULL default '-1',
`Contrast` mediumint(7) NOT NULL default '-1',

View File

@ -1,3 +1,9 @@
zoneminder (1.29.0+h264-wily-SNAPSHOT2016033101) wily; urgency=medium
*
-- Isaac Connor <iconnor@connortechnology.com> Thu, 31 Mar 2016 10:29:41 -0400
zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium
* include api, switch to cmake build

View File

@ -21,6 +21,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
,libphp-serialization-perl
,libsys-mmap-perl [!hurd-any]
,libwww-perl
,libx264-dev, libmp4v2-dev
# Unbundled (dh_linktree):
,libjs-jquery
,libjs-mootools
@ -61,6 +62,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,policykit-1
,rsyslog | system-log-daemon
,zip
,libmp4v2-2
Recommends: ${misc:Recommends}
,libapache2-mod-php5 | php5-fpm
,mysql-server | virtual-mysql-server

View File

@ -20,7 +20,7 @@
#include "zm.h"
#include "zm_camera.h"
Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
id( p_id ),
type( p_type ),
width( p_width),
@ -31,7 +31,8 @@ Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_co
hue( p_hue ),
colour( p_colour ),
contrast( p_contrast ),
capture( p_capture )
capture( p_capture ),
record_audio( p_record_audio )
{
pixels = width * height;
imagesize = pixels * colours;

5
src/zm_camera.h Executable file → Normal file
View File

@ -47,9 +47,10 @@ protected:
int colour;
int contrast;
bool capture;
bool record_audio;
public:
Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
virtual ~Camera();
int getId() const { return( id ); }
@ -74,7 +75,7 @@ public:
bool CanCapture() const { return( capture ); }
bool SupportsNativeVideo() const { return( type == FFMPEG_SRC ); }
bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); }
virtual int PrimeCapture() { return( 0 ); }
virtual int PreCapture()=0;

View File

@ -30,8 +30,8 @@ const char* content_type_match = "Content-Type:";
size_t content_length_match_len;
size_t content_type_match_len;
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
{
@ -313,6 +313,7 @@ int cURLCamera::PostCapture()
int cURLCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory )
{
Error("Capture and Record not implemented for the cURL camera type");
// Nothing to do here
return( 0 );
}

View File

@ -65,7 +65,7 @@ protected:
pthread_cond_t request_complete_cond;
public:
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~cURLCamera();
const std::string &Path() const { return( mPath ); }

0
src/zm_event.cpp Executable file → Normal file
View File

0
src/zm_event.h Executable file → Normal file
View File

82
src/zm_ffmpeg_camera.cpp Executable file → Normal file
View File

@ -36,8 +36,8 @@ extern "C"{
#include <pthread.h>
#endif
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
mPath( p_path ),
mMethod( p_method ),
mOptions( p_options )
@ -168,7 +168,7 @@ int FfmpegCamera::Capture( Image &image )
(avResult == -110)
)
{
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf);
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf );
ReopenFfmpeg();
}
@ -176,6 +176,7 @@ int FfmpegCamera::Capture( Image &image )
return( -1 );
}
Debug( 5, "Got packet from stream %d", packet.stream_index );
// What about audio stream? Maybe someday we could do sound detection...
if ( packet.stream_index == mVideoStreamId )
{
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
@ -187,8 +188,7 @@ int FfmpegCamera::Capture( Image &image )
Debug( 4, "Decoded video packet at frame %d", frameCount );
if ( frameComplete )
{
if ( frameComplete ) {
Debug( 3, "Got frame %d", frameCount );
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
@ -208,16 +208,18 @@ int FfmpegCamera::Capture( Image &image )
#endif // HAVE_LIBSWSCALE
frameCount++;
}
}
} // end if frameComplete
} else {
Debug( 4, "Different stream_index %d", packet.stream_index );
} // end if packet.stream_index == mVideoStreamId
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
av_packet_unref( &packet);
#else
av_free_packet( &packet );
#endif
}
} // end while ! frameComplete
return (0);
}
} // FfmpegCamera::Capture
int FfmpegCamera::PostCapture()
{
@ -312,6 +314,7 @@ int FfmpegCamera::OpenFfmpeg() {
// Find first video stream present
mVideoStreamId = -1;
mAudioStreamId = -1;
for (unsigned int i=0; i < mFormatContext->nb_streams; i++ )
{
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
@ -320,23 +323,32 @@ int FfmpegCamera::OpenFfmpeg() {
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
#endif
{
if ( mVideoStreamId == -1 ) {
mVideoStreamId = i;
break;
// if we break, then we won't find the audio stream
continue;
} else {
Debug(2, "Have another video stream." );
}
}
if(mAudioStreamId == -1) //FIXME best way to copy all other streams?
{
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
#else
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO )
#endif
{
if ( mAudioStreamId == -1 ) {
mAudioStreamId = i;
} else {
Debug(2, "Have another audio stream." );
}
}
}
if ( mVideoStreamId == -1 )
Fatal( "Unable to locate video stream in %s", mPath.c_str() );
if ( mAudioStreamId == -1 )
Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() );
Debug ( 1, "Found video stream" );
@ -521,17 +533,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
/* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if(directbuffer == NULL) {
if( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
return (-1);
}
int frameComplete = false;
while ( !frameComplete )
{
while ( !frameComplete ) {
int avResult = av_read_frame( mFormatContext, &packet );
if ( avResult < 0 )
{
if ( avResult < 0 ) {
char errbuf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE);
if (
@ -539,8 +549,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
(avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
// Check for Connection failure.
(avResult == -110)
)
{
) {
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf);
ReopenFfmpeg();
}
@ -549,8 +558,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
return( -1 );
}
Debug( 5, "Got packet from stream %d", packet.stream_index );
if ( packet.stream_index == mVideoStreamId )
{
if ( packet.stream_index == mVideoStreamId ) {
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 )
#else
@ -560,8 +568,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
Debug( 4, "Decoded video packet at frame %d", frameCount );
if ( frameComplete )
{
if ( frameComplete ) {
Debug( 3, "Got frame %d", frameCount );
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
@ -572,21 +579,23 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
//TODO I think we need to store the key frame location for seeking as part of the event
//Video recording
if(recording && !wasRecording){
if ( recording && !wasRecording ) {
//Instantiate the video storage module
videoStore = new VideoStore((const char *)event_file, "mp4", mFormatContext->streams[mVideoStreamId],mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],startTime);
wasRecording = true;
strcpy(oldDirectory, event_file);
}else if(!recording && wasRecording && videoStore){
} else if ( ( ! recording ) && wasRecording && videoStore ) {
Info("Deleting videoStore instance");
delete videoStore;
videoStore = NULL;
}
//The directory we are recording to is no longer tied to the current event. Need to re-init the videostore with the correct directory and start recording again
if(recording && wasRecording && (strcmp(oldDirectory, event_file)!=0) && (packet.flags & AV_PKT_FLAG_KEY) ){ //don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough?
if ( recording && wasRecording && (strcmp(oldDirectory, event_file) != 0 ) && (packet.flags & AV_PKT_FLAG_KEY) ) {
// don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...
// if we store our key frame location with the event will that be enough?
Info("Re-starting video storage module");
if(videoStore){
delete videoStore;
@ -597,7 +606,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
strcpy(oldDirectory, event_file);
}
if(videoStore && recording){
if ( videoStore && recording ) {
//Write the packet to our video store
int ret = videoStore->writeVideoFramePacket(&packet, mFormatContext->streams[mVideoStreamId]);//, &lastKeyframePkt);
if(ret<0){//Less than zero and we skipped a frame
@ -607,9 +616,9 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
}
#if HAVE_LIBSWSCALE
if(mConvertContext == NULL) {
if ( mConvertContext == NULL ) {
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
if(mConvertContext == NULL)
if ( mConvertContext == NULL )
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
}
@ -620,19 +629,24 @@ int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_fi
#endif // HAVE_LIBSWSCALE
frameCount++;
}
}else if(packet.stream_index == mAudioStreamId){//FIXME best way to copy all other streams
if(videoStore && recording){
} // end if frameComplete
} else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams
if ( videoStore && recording ) {
if ( record_audio ) {
Debug(4, "Recording audio packet" );
//Write the packet to our video store
int ret = videoStore->writeAudioFramePacket(&packet, mFormatContext->streams[packet.stream_index]); //FIXME no relevance of last key frame
if(ret<0){//Less than zero and we skipped a frame
if ( ret < 0 ) {//Less than zero and we skipped a frame
av_free_packet( &packet );
return 0;
}
} else {
Debug(4, "Not recording audio packet" );
}
}
}
av_free_packet( &packet );
}
} // end while ! frameComplete
return (frameCount);
}

2
src/zm_ffmpeg_camera.h Executable file → Normal file
View File

@ -73,7 +73,7 @@ protected:
int64_t startTime;
public:
FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~FfmpegCamera();
const std::string &Path() const { return( mPath ); }

View File

@ -34,7 +34,7 @@
#include "zm.h"
#include "zm_file_camera.h"
FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture )
FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio )
{
strncpy( path, p_path, sizeof(path) );
if ( capture )

View File

@ -36,7 +36,7 @@ protected:
char path[PATH_MAX];
public:
FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~FileCamera();
const char *Path() const { return( path ); }

View File

@ -61,8 +61,8 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
}
}
LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
mPath( p_path ),
mMethod( p_method ),
mOptions( p_options )

View File

@ -57,7 +57,7 @@ protected:
libvlc_media_player_t *mLibvlcMediaPlayer;
public:
LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~LibvlcCamera();
const std::string &Path() const { return( mPath ); }

View File

@ -286,8 +286,26 @@ AVFrame **LocalCamera::capturePictures = 0;
LocalCamera *LocalCamera::last_camera = NULL;
LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int p_standard, bool p_v4l_multi_buffer, unsigned int p_v4l_captures_per_frame, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras) :
Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
LocalCamera::LocalCamera(
int p_id,
const std::string &p_device,
int p_channel,
int p_standard,
bool p_v4l_multi_buffer,
unsigned int p_v4l_captures_per_frame,
const std::string &p_method,
int p_width,
int p_height,
int p_colours,
int p_palette,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio,
unsigned int p_extras) :
Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
device( p_device ),
channel( p_channel ),
standard( p_standard ),

View File

@ -116,7 +116,25 @@ protected:
static LocalCamera *last_camera;
public:
LocalCamera( int p_id, const std::string &device, int p_channel, int p_format, bool v4lmultibuffer, unsigned int v4lcapturesperframe, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras = 0);
LocalCamera(
int p_id,
const std::string &device,
int p_channel,
int p_format,
bool v4lmultibuffer,
unsigned int v4lcapturesperframe,
const std::string &p_method,
int p_width,
int p_height,
int p_colours,
int p_palette,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio,
unsigned int p_extras = 0);
~LocalCamera();
void Initialise();

284
src/zm_monitor.cpp Executable file → Normal file
View File

@ -75,8 +75,7 @@ std::vector<std::string> split(const std::string &s, char delim) {
Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id )
{
Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) {
strncpy( name, p_name, sizeof(name) );
#if ZM_MEM_MAPPED
@ -95,15 +94,12 @@ Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id )
connected = false;
}
Monitor::MonitorLink::~MonitorLink()
{
Monitor::MonitorLink::~MonitorLink() {
disconnect();
}
bool Monitor::MonitorLink::connect()
{
if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 )
{
bool Monitor::MonitorLink::connect() {
if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) {
last_connect_time = time( 0 );
mem_size = sizeof(SharedData) + sizeof(TriggerData);
@ -111,8 +107,7 @@ bool Monitor::MonitorLink::connect()
Debug( 1, "link.mem.size=%d", mem_size );
#if ZM_MEM_MAPPED
map_fd = open( mem_file, O_RDWR, (mode_t)0600 );
if ( map_fd < 0 )
{
if ( map_fd < 0 ) {
Debug( 3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno) );
disconnect();
return( false );
@ -125,44 +120,37 @@ bool Monitor::MonitorLink::connect()
}
struct stat map_stat;
if ( fstat( map_fd, &map_stat ) < 0 )
{
if ( fstat( map_fd, &map_stat ) < 0 ) {
Error( "Can't stat linked memory map file %s: %s", mem_file, strerror(errno) );
disconnect();
return( false );
}
if ( map_stat.st_size == 0 )
{
if ( map_stat.st_size == 0 ) {
Error( "Linked memory map file %s is empty: %s", mem_file, strerror(errno) );
disconnect();
return( false );
}
else if ( map_stat.st_size < mem_size )
{
} else if ( map_stat.st_size < mem_size ) {
Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size );
disconnect();
return( false );
}
mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 );
if ( mem_ptr == MAP_FAILED )
{
if ( mem_ptr == MAP_FAILED ) {
Error( "Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno) );
disconnect();
return( false );
}
#else // ZM_MEM_MAPPED
shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 );
if ( shm_id < 0 )
{
if ( shm_id < 0 ) {
Debug( 3, "Can't shmget link memory: %s", strerror(errno) );
connected = false;
return( false );
}
mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 );
if ( mem_ptr < 0 )
{
if ( mem_ptr < 0 ) {
Debug( 3, "Can't shmat link memory: %s", strerror(errno) );
connected = false;
return( false );
@ -172,8 +160,7 @@ bool Monitor::MonitorLink::connect()
shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
if ( !shared_data->valid )
{
if ( !shared_data->valid ) {
Debug( 3, "Linked memory not initialised by capture daemon" );
disconnect();
return( false );
@ -188,15 +175,12 @@ bool Monitor::MonitorLink::connect()
return( false );
}
bool Monitor::MonitorLink::disconnect()
{
if ( connected )
{
bool Monitor::MonitorLink::disconnect() {
if ( connected ) {
connected = false;
#if ZM_MEM_MAPPED
if ( mem_ptr > 0 )
{
if ( mem_ptr > 0 ) {
msync( mem_ptr, mem_size, MS_ASYNC );
munmap( mem_ptr, mem_size );
}
@ -206,25 +190,21 @@ bool Monitor::MonitorLink::disconnect()
map_fd = -1;
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 )
{
if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) {
Debug( 3, "Can't shmctl: %s", strerror(errno) );
return( false );
}
shm_id = 0;
if ( shm_data.shm_nattch <= 1 )
{
if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 )
{
if ( shm_data.shm_nattch <= 1 ) {
if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) {
Debug( 3, "Can't shmctl: %s", strerror(errno) );
return( false );
}
}
if ( shmdt( mem_ptr ) < 0 )
{
if ( shmdt( mem_ptr ) < 0 ) {
Debug( 3, "Can't shmdt: %s", strerror(errno) );
return( false );
}
@ -236,19 +216,15 @@ bool Monitor::MonitorLink::disconnect()
return( true );
}
bool Monitor::MonitorLink::isAlarmed()
{
if ( !connected )
{
bool Monitor::MonitorLink::isAlarmed() {
if ( !connected ) {
return( false );
}
return( shared_data->state == ALARM );
}
bool Monitor::MonitorLink::inAlarm()
{
if ( !connected )
{
bool Monitor::MonitorLink::inAlarm() {
if ( !connected ) {
return( false );
}
return( shared_data->state == ALARM || shared_data->state == ALERT );
@ -256,12 +232,9 @@ bool Monitor::MonitorLink::inAlarm()
bool Monitor::MonitorLink::hasAlarmed()
{
if ( shared_data->state == ALARM )
{
if ( shared_data->state == ALARM ) {
return( true );
}
else if( shared_data->last_event != (unsigned int)last_event )
{
} else if ( shared_data->last_event != (unsigned int)last_event ) {
last_event = shared_data->last_event;
}
return( false );
@ -280,6 +253,7 @@ Monitor::Monitor(
int p_savejpegs,
int p_videowriter,
std::string p_encoderparams,
bool p_record_audio,
const char *p_event_prefix,
const char *p_label_format,
const Coord &p_label_coord,
@ -317,6 +291,7 @@ Monitor::Monitor(
savejpegspref( p_savejpegs ),
videowriterpref( p_videowriter ),
encoderparams( p_encoderparams ),
record_audio( p_record_audio ),
label_coord( p_label_coord ),
label_size( p_label_size ),
image_buffer_count( p_image_buffer_count ),
@ -450,16 +425,15 @@ Monitor::Monitor(
shared_data->alarm_y = -1;
}
if ( ( ! mem_ptr ) || ! shared_data->valid )
{
if ( purpose != QUERY )
{
if ( ( ! mem_ptr ) || ! shared_data->valid ) {
if ( purpose != QUERY ) {
Error( "Shared data not initialised by capture daemon for monitor %s", name );
exit( -1 );
}
}
// Will this not happen every time a monitor is instantiated? Seems like all the calls to the Monitor constructor pass a zero for n_zones, then load zones after..
// In my storage areas branch, I took this out.. and didn't notice any problems.
if ( !n_zones ) {
Debug( 1, "Monitor %s has no zones, adding one.", name );
n_zones = 1;
@ -475,8 +449,7 @@ Monitor::Monitor(
Debug( 1, "Monitor %s LBF = '%s', LBX = %d, LBY = %d, LBS = %d", name, label_format, label_coord.X(), label_coord.Y(), label_size );
Debug( 1, "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, EAF = %d, FRI = %d, RBP = %d, ARBP = %d, FM = %d", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion );
if ( purpose == ANALYSIS )
{
if ( purpose == ANALYSIS ) {
static char path[PATH_MAX];
strncpy( path, config.dir_events, sizeof(path) );
@ -484,8 +457,7 @@ Monitor::Monitor(
struct stat statbuf;
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR )
{
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir( path, 0755 ) )
{
Error( "Can't make %s: %s", path, strerror(errno));
@ -496,10 +468,8 @@ Monitor::Monitor(
errno = 0;
stat( path, &statbuf );
if ( errno == ENOENT || errno == ENOTDIR )
{
if ( mkdir( path, 0755 ) )
{
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir( path, 0755 ) ) {
Error( "Can't make %s: %s", path, strerror(errno));
}
char temp_path[PATH_MAX];
@ -513,8 +483,7 @@ Monitor::Monitor(
}
while( shared_data->last_write_index == (unsigned int)image_buffer_count
&& shared_data->last_write_time == 0)
{
&& shared_data->last_write_time == 0) {
Warning( "Waiting for capture daemon" );
sleep( 1 );
}
@ -573,8 +542,7 @@ bool Monitor::connect() {
exit( -1 );
}
mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 );
if ( mem_ptr < 0 )
{
if ( mem_ptr < 0 ) {
Error( "Can't shmat: %s", strerror(errno));
exit( -1 );
}
@ -591,29 +559,25 @@ bool Monitor::connect() {
shared_images = (uint8_t*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16)));
}
image_buffer = new Snapshot[image_buffer_count];
for ( int i = 0; i < image_buffer_count; i++ )
{
for ( int i = 0; i < image_buffer_count; i++ ) {
image_buffer[i].timestamp = &(shared_timestamps[i]);
image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) );
image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */
}
if ( (deinterlacing & 0xff) == 4)
{
if ( (deinterlacing & 0xff) == 4) {
/* Four field motion adaptive deinterlacing in use */
/* Allocate a buffer for the next image */
next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
next_buffer.timestamp = new struct timeval;
}
if ( ( purpose == ANALYSIS ) && analysis_fps )
{
if ( ( purpose == ANALYSIS ) && analysis_fps ) {
// Size of pre event buffer must be greater than pre_event_count
// if alarm_frame_count > 1, because in this case the buffer contains
// alarmed images that must be discarded when event is created
pre_event_buffer_count = pre_event_count + alarm_frame_count - 1;
pre_event_buffer = new Snapshot[pre_event_buffer_count];
for ( int i = 0; i < pre_event_buffer_count; i++ )
{
for ( int i = 0; i < pre_event_buffer_count; i++ ) {
pre_event_buffer[i].timestamp = new struct timeval;
pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
}
@ -1892,6 +1856,7 @@ void Monitor::Reload()
closeEvent();
static char sql[ZM_SQL_MED_BUFSIZ];
// This seems to have fallen out of date.
snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id );
if ( mysql_query( &dbconn, sql ) )
@ -2084,7 +2049,7 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors )
#if ZM_HAS_V4L
int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose )
{
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'";
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'";
if ( device[0] ) {
sql += " AND Device='";
sql += device;
@ -2148,6 +2113,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
bool record_audio = (*dbrow[col] != '0'); col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
@ -2209,6 +2175,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
hue,
colour,
purpose==CAPTURE,
record_audio,
extras
);
@ -2225,6 +2192,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
savejpegs,
videowriter,
encoderparams,
record_audio,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2272,7 +2240,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose )
{
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'";
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'";
if ( staticConfig.SERVER_ID ) {
sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID );
}
@ -2318,6 +2286,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
bool record_audio = (*dbrow[col] != '0'); col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
@ -2354,8 +2323,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
int cam_height = ((orientation==ROTATE_90||orientation==ROTATE_270)?width:height);
Camera *camera = 0;
if ( protocol == "http" )
{
if ( protocol == "http" ) {
camera = new RemoteCameraHttp(
id,
method,
@ -2369,12 +2337,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
}
#if HAVE_LIBAVFORMAT
else if ( protocol == "rtsp" )
{
else if ( protocol == "rtsp" ) {
camera = new RemoteCameraRtsp(
id,
method,
@ -2389,12 +2357,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
}
#endif // HAVE_LIBAVFORMAT
else
{
else {
Fatal( "Unexpected remote camera protocol '%s'", protocol.c_str() );
}
@ -2411,6 +2379,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
savejpegs,
videowriter,
encoderparams,
record_audio,
event_prefix.c_str(),
label_format.c_str(),
Coord( label_x, label_y ),
@ -2437,7 +2406,6 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
purpose,
0,
0
);
Zone **zones = 0;
int n_zones = Zone::Load( monitors[i], zones );
@ -2456,9 +2424,8 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c
return( n_monitors );
}
int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose )
{
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'";
int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) {
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'";
if ( file[0] ) {
sql += " AND Path='";
sql += file;
@ -2501,6 +2468,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
bool record_audio = (*dbrow[col] != '0'); col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
@ -2546,7 +2514,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
monitors[i] = new Monitor(
@ -2562,6 +2531,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
savejpegs,
videowriter,
encoderparams,
record_audio,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2609,7 +2579,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu
#if HAVE_LIBAVFORMAT
int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose )
{
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'";
std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'";
if ( file[0] ) {
sql += " AND Path = '";
sql += file;
@ -2629,8 +2599,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
Debug( 1, "Got %d monitors", n_monitors );
delete[] monitors;
monitors = new Monitor *[n_monitors];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
{
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) {
int col = 0;
int id = atoi(dbrow[col]); col++;
@ -2654,6 +2623,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
bool record_audio = (*dbrow[col] != '0'); col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
@ -2701,7 +2671,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
monitors[i] = new Monitor(
@ -2717,6 +2688,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
savejpegs,
videowriter,
encoderparams,
record_audio,
event_prefix,
label_format,
Coord( label_x, label_y ),
@ -2762,9 +2734,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
}
#endif // HAVE_LIBAVFORMAT
Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose )
{
std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id );
Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) {
std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id );
MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() );
if ( ! dbrow ) {
@ -2802,7 +2773,7 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose )
} else {
v4l_captures_per_frame = config.captures_per_frame;
}
Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
col++;
std::string protocol = dbrow[col]; col++;
@ -2824,6 +2795,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int savejpegs = atoi(dbrow[col]); col++;
int videowriter = atoi(dbrow[col]); col++;
std::string encoderparams = dbrow[col]; col++;
bool record_audio = (*dbrow[col] != '0'); col++;
int brightness = atoi(dbrow[col]); col++;
int contrast = atoi(dbrow[col]); col++;
@ -2869,8 +2841,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
int extras = (deinterlacing>>24)&0xff;
Camera *camera = 0;
if ( type == "Local" )
{
if ( type == "Local" ) {
#if ZM_HAS_V4L
camera = new LocalCamera(
id,
@ -2889,16 +2860,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
hue,
colour,
purpose==CAPTURE,
record_audio,
extras
);
#else // ZM_HAS_V4L
Fatal( "You must have video4linux libraries and headers installed to use local analog or USB cameras for monitor %d", id );
#endif // ZM_HAS_V4L
}
else if ( type == "Remote" )
{
if ( protocol == "http" )
{
} else if ( type == "Remote" ) {
if ( protocol == "http" ) {
camera = new RemoteCameraHttp(
id,
method.c_str(),
@ -2912,11 +2881,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
}
else if ( protocol == "rtsp" )
{
} else if ( protocol == "rtsp" ) {
#if HAVE_LIBAVFORMAT
camera = new RemoteCameraRtsp(
id,
@ -2932,19 +2900,16 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
#else // HAVE_LIBAVFORMAT
Fatal( "You must have ffmpeg libraries installed to use remote camera protocol '%s' for monitor %d", protocol.c_str(), id );
#endif // HAVE_LIBAVFORMAT
}
else
{
} else {
Fatal( "Unexpected remote camera protocol '%s' for monitor %d", protocol.c_str(), id );
}
}
else if ( type == "File" )
{
} else if ( type == "File" ) {
camera = new FileCamera(
id,
path.c_str(),
@ -2955,11 +2920,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
}
else if ( type == "Ffmpeg" )
{
} else if ( type == "Ffmpeg" ) {
#if HAVE_LIBAVFORMAT
camera = new FfmpegCamera(
id,
@ -2973,14 +2937,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
#else // HAVE_LIBAVFORMAT
Fatal( "You must have ffmpeg libraries installed to use ffmpeg cameras for monitor %d", id );
#endif // HAVE_LIBAVFORMAT
}
else if (type == "Libvlc")
{
} else if (type == "Libvlc") {
#if HAVE_LIBVLC
camera = new LibvlcCamera(
id,
@ -2994,14 +2957,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
#else // HAVE_LIBVLC
Fatal( "You must have vlc libraries installed to use vlc cameras for monitor %d", id );
#endif // HAVE_LIBVLC
}
else if ( type == "cURL" )
{
} else if ( type == "cURL" ) {
#if HAVE_LIBCURL
camera = new cURLCamera(
id,
@ -3015,14 +2977,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
contrast,
hue,
colour,
purpose==CAPTURE
purpose==CAPTURE,
record_audio
);
#else // HAVE_LIBCURL
Fatal( "You must have libcurl installed to use ffmpeg cameras for monitor %d", id );
#endif // HAVE_LIBCURL
}
else
{
} else {
Fatal( "Bogus monitor type '%s' for monitor %d", type.c_str(), id );
}
monitor = new Monitor(
@ -3038,6 +2999,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
savejpegs,
videowriter,
encoderparams,
record_audio,
event_prefix.c_str(),
label_format.c_str(),
Coord( label_x, label_y ),
@ -3068,8 +3030,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame );
);
int n_zones = 0;
if ( load_zones )
{
if ( load_zones ) {
Zone **zones = 0;
n_zones = Zone::Load( monitor, zones );
monitor->AddZones( n_zones, zones );
@ -3123,8 +3084,7 @@ int Monitor::Capture()
captureResult = 0;
}
if ( captureResult != 0 )
{
if ( captureResult != 0 ) {
// Unable to capture image for temporary reason
// Fake a signal loss image
Rgb signalcolor;
@ -3135,8 +3095,7 @@ int Monitor::Capture()
captureResult = 1;
}
if ( captureResult == 1 )
{
if ( captureResult == 1 ) {
/* Deinterlacing */
if ( (deinterlacing & 0xff) == 1 ) {
@ -3152,48 +3111,41 @@ int Monitor::Capture()
}
if ( orientation != ROTATE_0 )
{
switch ( orientation )
{
case ROTATE_0 :
{
if ( orientation != ROTATE_0 ) {
switch ( orientation ) {
case ROTATE_0 : {
// No action required
break;
}
case ROTATE_90 :
case ROTATE_180 :
case ROTATE_270 :
{
case ROTATE_270 : {
capture_image->Rotate( (orientation-1)*90 );
break;
}
case FLIP_HORI :
case FLIP_VERT :
{
case FLIP_VERT : {
capture_image->Flip( orientation==FLIP_HORI );
break;
}
}
}
} // end if captureResults == 1
}
// if true? let's get rid of this.
if ( true ) {
if ( capture_image->Size() > camera->ImageSize() )
{
if ( capture_image->Size() > camera->ImageSize() ) {
Error( "Captured image %d does not match expected size %d check width, height and colour depth",capture_image->Size(),camera->ImageSize() );
return( -1 );
}
if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) )
{
if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) ) {
Warning( "Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", index, image_count );
time_t now = time(0);
double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec);
time_t last_read_delta = now - shared_data->last_read_time;
if ( last_read_delta > (image_buffer_count/approxFps) )
{
if ( last_read_delta > (image_buffer_count/approxFps) ) {
Warning( "Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta )
shared_data->last_read_index = image_buffer_count;
}
@ -3203,8 +3155,7 @@ int Monitor::Capture()
capture_image->MaskPrivacy( privacy_bitmask );
gettimeofday( image_buffer[index].timestamp, NULL );
if ( config.timestamp_on_capture )
{
if ( config.timestamp_on_capture ) {
TimestampImage( capture_image, image_buffer[index].timestamp );
}
shared_data->signal = CheckSignal(capture_image);
@ -3213,8 +3164,7 @@ int Monitor::Capture()
image_count++;
if ( image_count && fps_report_interval && !(image_count%fps_report_interval) )
{
if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) {
time_t now = image_buffer[index].timestamp->tv_sec;
fps = double(fps_report_interval)/(now-last_fps_time);
//Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time );
@ -3223,16 +3173,14 @@ int Monitor::Capture()
last_fps_time = now;
}
if ( shared_data->action & GET_SETTINGS )
{
if ( shared_data->action & GET_SETTINGS ) {
shared_data->brightness = camera->Brightness();
shared_data->hue = camera->Hue();
shared_data->colour = camera->Colour();
shared_data->contrast = camera->Contrast();
shared_data->action &= ~GET_SETTINGS;
}
if ( shared_data->action & SET_SETTINGS )
{
if ( shared_data->action & SET_SETTINGS ) {
camera->Brightness( shared_data->brightness );
camera->Hue( shared_data->hue );
camera->Colour( shared_data->colour );
@ -3292,10 +3240,8 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c
bool Monitor::closeEvent()
{
video_store_data->recording = false;
if ( event )
{
if ( function == RECORD || function == MOCORD )
{
if ( event ) {
if ( function == RECORD || function == MOCORD ) {
gettimeofday( &(event->EndTime()), NULL );
}
delete event;
@ -3747,7 +3693,7 @@ bool Monitor::DumpSettings( char *output, bool verbose )
zones[i]->DumpSettings( output+strlen(output), verbose );
}
return( true );
}
} // bool Monitor::DumpSettings( char *output, bool verbose )
bool MonitorStream::checkSwapPath( const char *path, bool create_path )
{

View File

@ -134,8 +134,7 @@ protected:
typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState;
/* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */
typedef struct
{
typedef struct {
uint32_t size;
uint32_t trigger_state;
uint32_t trigger_score;
@ -146,8 +145,7 @@ protected:
} TriggerData;
/* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */
struct Snapshot
{
struct Snapshot {
struct timeval *timestamp;
Image *image;
void* padding;
@ -156,8 +154,7 @@ protected:
//TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat
#if 1
//sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit
typedef struct
{
typedef struct {
uint32_t size;
char event_file[4096];
uint32_t recording; //bool arch dependent so use uint32 instead
@ -167,8 +164,7 @@ protected:
#endif // HAVE_LIBAVFORMAT
class MonitorLink
{
class MonitorLink {
protected:
unsigned int id;
char name[64];
@ -196,21 +192,17 @@ protected:
MonitorLink( int p_id, const char *p_name );
~MonitorLink();
inline int Id() const
{
inline int Id() const {
return( id );
}
inline const char *Name() const
{
inline const char *Name() const {
return( name );
}
inline bool isConnected() const
{
inline bool isConnected() const {
return( connected );
}
inline time_t getLastConnectTime() const
{
inline time_t getLastConnectTime() const {
return( last_connect_time );
}
@ -240,6 +232,7 @@ protected:
int videowriterpref;
std::string encoderparams;
std::vector<EncoderParameter_t> encoderparamsvec;
bool record_audio; // Whether to store the audio that we receive
int brightness; // The statically saved brightness of the camera
int contrast; // The statically saved contrast of the camera
@ -329,54 +322,85 @@ protected:
public:
// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info.
//bool OurCheckAlarms( Zone *zone, const Image *pImage );
Monitor( int p_id, const char *p_name, unsigned int p_server_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, int p_savejpegs, int p_videowriter, std::string p_encoderparams, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int label_size, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, double p_analysis_fps, unsigned int p_analysis_update_delay, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, bool p_embed_exif, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 );
Monitor(
int p_id,
const char *p_name,
unsigned int p_server_id,
int p_function,
bool p_enabled,
const char *p_linked_monitors,
Camera *p_camera,
int p_orientation,
unsigned int p_deinterlacing,
int p_savejpegs,
int p_videowriter,
std::string p_encoderparams,
bool p_record_audio,
const char *p_event_prefix,
const char *p_label_format,
const Coord &p_label_coord,
int label_size,
int p_image_buffer_count,
int p_warmup_count,
int p_pre_event_count,
int p_post_event_count,
int p_stream_replay_buffer,
int p_alarm_frame_count,
int p_section_length,
int p_frame_skip,
int p_motion_frame_skip,
double p_analysis_fps,
unsigned int p_analysis_update_delay,
int p_capture_delay,
int p_alarm_capture_delay,
int p_fps_report_interval,
int p_ref_blend_perc,
int p_alarm_ref_blend_perc,
bool p_track_motion,
Rgb p_signal_check_colour,
bool p_embed_exif,
Purpose p_purpose,
int p_n_zones=0,
Zone *p_zones[]=0
);
~Monitor();
void AddZones( int p_n_zones, Zone *p_zones[] );
void AddPrivacyBitmask( Zone *p_zones[] );
bool connect();
inline int ShmValid() const
{
inline int ShmValid() const {
return( shared_data->valid );
}
inline int Id() const
{
inline int Id() const {
return( id );
}
inline const char *Name() const
{
inline const char *Name() const {
return( name );
}
inline Function GetFunction() const
{
inline Function GetFunction() const {
return( function );
}
inline bool Enabled()
{
inline bool Enabled() {
if ( function <= MONITOR )
return( false );
return( enabled );
}
inline const char *EventPrefix() const
{
inline const char *EventPrefix() const {
return( event_prefix );
}
inline bool Ready()
{
inline bool Ready() {
if ( function <= MONITOR )
return( false );
return( image_count > ready_count );
}
inline bool Active()
{
inline bool Active() {
if ( function <= MONITOR )
return( false );
return( enabled && shared_data->active );
}
inline bool Exif()
{
inline bool Exif() {
return( embed_exif );
}
@ -417,17 +441,14 @@ public:
int actionColour( int p_colour=-1 );
int actionContrast( int p_contrast=-1 );
inline int PrimeCapture()
{
inline int PrimeCapture() {
return( camera->PrimeCapture() );
}
inline int PreCapture()
{
inline int PreCapture() {
return( camera->PreCapture() );
}
int Capture();
int PostCapture()
{
int PostCapture() {
return( camera->PostCapture() );
}
@ -470,8 +491,7 @@ public:
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
class MonitorStream : public StreamBase
{
class MonitorStream : public StreamBase {
protected:
typedef struct SwapImage {
bool valid;
@ -502,19 +522,15 @@ protected:
void processCommand( const CmdMsg *msg );
public:
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 )
{
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) {
}
void setStreamBuffer( int p_playback_buffer )
{
void setStreamBuffer( int p_playback_buffer ) {
playback_buffer = p_playback_buffer;
}
void setStreamTTL( time_t p_ttl )
{
void setStreamTTL( time_t p_ttl ) {
ttl = p_ttl;
}
bool setStreamStart( int monitor_id )
{
bool setStreamStart( int monitor_id ) {
return loadMonitor( monitor_id );
}
void runStream();

View File

@ -21,8 +21,23 @@
#include "zm_utils.h"
RemoteCamera::RemoteCamera( int p_id, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera( p_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
RemoteCamera::RemoteCamera(
int p_id,
const std::string &p_protocol,
const std::string &p_host,
const std::string &p_port,
const std::string &p_path,
int p_width,
int p_height,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio
) :
Camera( p_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
protocol( p_protocol ),
host( p_host ),
port( p_port ),

View File

@ -55,7 +55,22 @@ protected:
struct addrinfo *hp;
public:
RemoteCamera( int p_id, const std::string &p_proto, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
RemoteCamera(
int p_id,
const std::string &p_proto,
const std::string &p_host,
const std::string &p_port,
const std::string &p_path,
int p_width,
int p_height,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio
);
virtual ~RemoteCamera();
const std::string &Protocol() const { return( protocol ); }

View File

@ -31,8 +31,8 @@
#include <sys/filio.h> // FIONREAD and friends
#endif
RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
RemoteCamera( p_id, "http", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture )
RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
RemoteCamera( p_id, "http", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio )
{
sd = -1;

View File

@ -45,7 +45,7 @@ protected:
enum { SIMPLE, REGEXP } method;
public:
RemoteCameraHttp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
RemoteCameraHttp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~RemoteCameraHttp();
void Initialise();

View File

@ -28,8 +28,8 @@
#include <sys/types.h>
#include <sys/socket.h>
RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
RemoteCamera( p_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ),
RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
RemoteCamera( p_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
rtsp_describe( p_rtsp_describe ),
rtspThread( 0 )
@ -52,11 +52,14 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const
mFormatContext = NULL;
mVideoStreamId = -1;
mAudioStreamId = -1;
mCodecContext = NULL;
mCodec = NULL;
mRawFrame = NULL;
mFrame = NULL;
frameCount = 0;
wasRecording = false;
startTime=0;
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
@ -113,6 +116,8 @@ void RemoteCameraRtsp::Initialise()
int max_size = width*height*colours;
// This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some
// decent data on how large a buffer is really needed.
buffer.size( max_size );
if ( logDebugging() )
@ -168,6 +173,7 @@ int RemoteCameraRtsp::PrimeCapture()
// Find first video stream present
mVideoStreamId = -1;
// Find the first video stream.
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ )
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
@ -236,8 +242,7 @@ int RemoteCameraRtsp::PrimeCapture()
return( 0 );
}
int RemoteCameraRtsp::PreCapture()
{
int RemoteCameraRtsp::PreCapture() {
if ( !rtspThread->isRunning() )
return( -1 );
if ( !rtspThread->hasSources() )
@ -248,8 +253,7 @@ int RemoteCameraRtsp::PreCapture()
return( 0 );
}
int RemoteCameraRtsp::Capture( Image &image )
{
int RemoteCameraRtsp::Capture( Image &image ) {
AVPacket packet;
uint8_t* directbuffer;
int frameComplete = false;
@ -261,14 +265,12 @@ int RemoteCameraRtsp::Capture( Image &image )
return (-1);
}
while ( true )
{
while ( true ) {
buffer.clear();
if ( !rtspThread->isRunning() )
return (-1);
if ( rtspThread->getFrame( buffer ) )
{
if ( rtspThread->getFrame( buffer ) ) {
Debug( 3, "Read frame %d bytes", buffer.size() );
Debug( 4, "Address %p", buffer.head() );
Hexdump( 4, buffer.head(), 16 );
@ -276,18 +278,17 @@ int RemoteCameraRtsp::Capture( Image &image )
if ( !buffer.size() )
return( -1 );
if(mCodecContext->codec_id == AV_CODEC_ID_H264)
{
if(mCodecContext->codec_id == AV_CODEC_ID_H264) {
// SPS and PPS frames should be saved and appended to IDR frames
int nalType = (buffer.head()[3] & 0x1f);
// SPS
// SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures
if(nalType == 7)
{
lastSps = buffer;
continue;
}
// PPS
// PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence
else if(nalType == 8)
{
lastPps = buffer;
@ -303,17 +304,17 @@ int RemoteCameraRtsp::Capture( Image &image )
av_init_packet( &packet );
while ( !frameComplete && buffer.size() > 0 )
{
while ( !frameComplete && buffer.size() > 0 ) {
packet.data = buffer.head();
packet.size = buffer.size();
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
// So I think this is the magic decode step. Result is a raw image?
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet );
#else
#else
int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size );
#endif
if ( len < 0 )
{
#endif
if ( len < 0 ) {
Error( "Error while decoding frame %d", frameCount );
Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() );
buffer.clear();
@ -324,13 +325,165 @@ int RemoteCameraRtsp::Capture( Image &image )
//Hexdump( 0, buffer.head(), buffer.size() );
buffer -= len;
}
// At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer?
if ( frameComplete ) {
Debug( 3, "Got frame %d", frameCount );
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height );
#if HAVE_LIBSWSCALE
if(mConvertContext == NULL) {
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
if(mConvertContext == NULL)
Fatal( "Unable to create conversion context");
}
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
#else // HAVE_LIBSWSCALE
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
#endif // HAVE_LIBSWSCALE
frameCount++;
} /* frame complete */
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
av_packet_unref( &packet );
#else
av_free_packet( &packet );
#endif
} /* getFrame() */
if(frameComplete)
return (0);
} // end while true
// can never get here.
return (0) ;
}
//Function to handle capture and store
int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) {
AVPacket packet;
uint8_t* directbuffer;
int frameComplete = false;
/* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if(directbuffer == NULL) {
Error("Failed requesting writeable buffer for the captured image.");
return (-1);
}
while ( true ) {
buffer.clear();
if ( !rtspThread->isRunning() )
return (-1);
if ( rtspThread->getFrame( buffer ) ) {
Debug( 3, "Read frame %d bytes", buffer.size() );
Debug( 4, "Address %p", buffer.head() );
Hexdump( 4, buffer.head(), 16 );
if ( !buffer.size() )
return( -1 );
if(mCodecContext->codec_id == AV_CODEC_ID_H264) {
// SPS and PPS frames should be saved and appended to IDR frames
int nalType = (buffer.head()[3] & 0x1f);
// SPS
if(nalType == 7) {
lastSps = buffer;
continue;
}
// PPS
else if(nalType == 8) {
lastPps = buffer;
continue;
}
// IDR
else if(nalType == 5) {
buffer += lastSps;
buffer += lastPps;
}
} // end if H264, what about other codecs?
av_init_packet( &packet );
// Why are we checking for it being the video stream
if ( packet.stream_index == mVideoStreamId ) {
while ( !frameComplete && buffer.size() > 0 ) {
packet.data = buffer.head();
packet.size = buffer.size();
// So this does the decode
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet );
#else
int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size );
#endif
if ( len < 0 ) {
Error( "Error while decoding frame %d", frameCount );
Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() );
buffer.clear();
continue;
}
Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() );
//if ( buffer.size() < 400 )
//Hexdump( 0, buffer.head(), buffer.size() );
buffer -= len;
} // end while get & decode a frame
if ( frameComplete ) {
Debug( 3, "Got frame %d", frameCount );
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height );
//Video recording
if ( recording && !wasRecording ) {
//Instantiate the video storage module
videoStore = new VideoStore((const char *)event_file, "mp4", mFormatContext->streams[mVideoStreamId],mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],startTime);
wasRecording = true;
strcpy(oldDirectory, event_file);
} else if ( !recording && wasRecording && videoStore ) {
// Why are we deleting the videostore? Becase for soem reason we are no longer recording? How does that happen?
Info("Deleting videoStore instance");
delete videoStore;
videoStore = NULL;
}
//The directory we are recording to is no longer tied to the current event. Need to re-init the videostore with the correct directory and start recording again
if ( recording && wasRecording && (strcmp(oldDirectory, event_file)!=0) && (packet.flags & AV_PKT_FLAG_KEY) ) {
//don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough?
Info("Re-starting video storage module");
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
}
videoStore = new VideoStore((const char *)event_file, "mp4", mFormatContext->streams[mVideoStreamId],mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],startTime);
strcpy( oldDirectory, event_file );
}
if ( videoStore && recording ) {
//Write the packet to our video store
int ret = videoStore->writeVideoFramePacket(&packet, mFormatContext->streams[mVideoStreamId]);//, &lastKeyframePkt);
if ( ret < 0 ) {//Less than zero and we skipped a frame
av_free_packet( &packet );
return 0;
}
}
#if HAVE_LIBSWSCALE
if(mConvertContext == NULL) {
@ -349,9 +502,29 @@ int RemoteCameraRtsp::Capture( Image &image )
frameCount++;
} /* frame complete */
} else if ( packet.stream_index == mAudioStreamId ) {
Debug( 4, "Got audio packet" );
if ( videoStore && recording ) {
if ( record_audio ) {
Debug( 4, "Storing Audio packet" );
//Write the packet to our video store
int ret = videoStore->writeAudioFramePacket(&packet, mFormatContext->streams[packet.stream_index]); //FIXME no relevance of last key frame
if ( ret < 0 ) { //Less than zero and we skipped a frame
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
av_packet_unref( &packet );
#else
av_free_packet( &packet );
#endif
return 0;
}
} else {
Debug( 4, "Not storing audio" );
}
}
} // end if video or audio packet
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
av_packet_unref( &packet);
av_packet_unref( &packet );
#else
av_free_packet( &packet );
#endif
@ -359,10 +532,9 @@ int RemoteCameraRtsp::Capture( Image &image )
if(frameComplete)
return (0);
}
} // end while true
return (0) ;
}
} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file )
int RemoteCameraRtsp::PostCapture()
{

View File

@ -26,6 +26,7 @@
#include "zm_utils.h"
#include "zm_rtsp.h"
#include "zm_ffmpeg.h"
#include "zm_videostore.h"
//
// Class representing 'rtsp' cameras, i.e. those which are
@ -55,19 +56,24 @@ protected:
#if HAVE_LIBAVFORMAT
AVFormatContext *mFormatContext;
int mVideoStreamId;
int mAudioStreamId;
AVCodecContext *mCodecContext;
AVCodec *mCodec;
AVFrame *mRawFrame;
AVFrame *mFrame;
_AVPIXELFORMAT imagePixFormat;
#endif // HAVE_LIBAVFORMAT
bool wasRecording;
VideoStore *videoStore;
char oldDirectory[4096];
int64_t startTime;
#if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext;
#endif
public:
RemoteCameraRtsp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
RemoteCameraRtsp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
~RemoteCameraRtsp();
void Initialise();
@ -79,7 +85,7 @@ public:
int PreCapture();
int Capture( Image &image );
int PostCapture();
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
};
#endif // ZM_REMOTE_CAMERA_RTSP_H

View File

@ -262,6 +262,11 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
int rtpHeaderSize = 12 + rtpHeader->cc * 4;
// No need to check for nal type as non fragmented packets already have 001 start sequence appended
bool h264FragmentEnd = (mCodecId == AV_CODEC_ID_H264) && (packet[rtpHeaderSize+1] & 0x40);
// M stands for Market, it is the 8th bit
// The interpretation of the marker is defined by a profile. It is intended
// to allow significant events such as frame boundaries to be marked in the
// packet stream. A profile may define additional marker bits or specify
// that there is no marker bit by changing the number of bits in the payload type field.
bool thisM = rtpHeader->m || h264FragmentEnd;
if ( updateSeq( ntohs(rtpHeader->seqN) ) )
@ -275,15 +280,18 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
if( mCodecId == AV_CODEC_ID_H264 )
{
int nalType = (packet[rtpHeaderSize] & 0x1f);
Debug( 3, "Have H264 frame: nal type is %d", nalType );
switch (nalType)
{
case 24:
case 24: // STAP-A
{
extraHeader = 2;
break;
}
case 25: case 26: case 27:
case 25: // STAP-B
case 26: // MTAP-16
case 27: // MTAP-24
{
extraHeader = 3;
break;
@ -304,6 +312,9 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
extraHeader = 2;
break;
}
default: {
Debug(3, "Unhandled nalType %d", nalType );
}
}
// Append NAL frame start code
@ -311,6 +322,8 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
mFrame.append( "\x0\x0\x1", 3 );
}
mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader );
} else {
Debug( 3, "NOT H264 frame: type is %d", mCodecId );
}
Hexdump( 4, mFrame.head(), 16 );

View File

@ -64,7 +64,7 @@ X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, con
if(zm_pf == 0) {
Error("Unable to match ffmpeg pixelformat");
}
codec_pf = AV_PIX_FMT_YUV420P;
codec_pf = PIX_FMT_YUV420P;
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);

View File

@ -120,8 +120,8 @@ protected:
/* SWScale */
SWScale swscaleobj;
enum _AVPIXELFORMAT zm_pf;
enum _AVPIXELFORMAT codec_pf;
enum PixelFormat zm_pf;
enum PixelFormat codec_pf;
size_t codec_imgsize;
size_t zm_imgsize;

0
src/zm_videostore.cpp Executable file → Normal file
View File

0
src/zm_videostore.h Executable file → Normal file
View File

View File

@ -1 +1 @@
1.29.0
1.30.0

View File

@ -1,33 +0,0 @@
# Define the line ending behavior of the different file extensions
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
*.php text
*.default text
*.ctp text
*.sql text
*.md text
*.po text
*.js text
*.css text
*.ini text
*.properties text
*.txt text
*.xml text
*.yml text
.htaccess text
# Declare files that will always have CRLF line endings on checkout.
*.bat eol=crlf
# Declare files that will always have LF line endings on checkout.
*.pem eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.mo binary

21
web/api/.gitignore vendored
View File

@ -1,21 +0,0 @@
# User specific & automatically generated files #
#################################################
/app/Config/database.php
/app/tmp
/lib/Cake/Console/Templates/skel/tmp/
/plugins
/vendors
/build
/dist
/tags
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db

View File

@ -1153,30 +1153,38 @@ function zmaCheck( $monitor )
function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false )
{
$eventPath = getEventPath( $event );
$eventPath = ZM_DIR_EVENTS.'/'.getEventPath( $event );
if ( !is_array($frame) )
$frame = array( 'FrameId'=>$frame, 'Type'=>'' );
//echo "S:$scale, CO:$captureOnly<br>";
$currEvent = dbFetchOne( 'SELECT M.SaveJPEGs FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id = '.$event['Id'] );
if ( $currEvent['SaveJPEGs'] == "4" )
if ( file_exists( $eventPath.'/snapshot.jpg' ) ) {
$captImage = "snapshot.jpg";
else
} else {
$captImage = sprintf( "%0".ZM_EVENT_IMAGE_DIGITS."d-capture.jpg", $frame['FrameId'] );
if ( ! file_exists( $eventPath.'/'.$captImage ) ) {
# Generate the frame JPG
if ( $event['DefaultVideo'] ) {
$command ='ffmpeg -v 0 -i '.$eventPath.'/'.$Event->DefaultVideo().' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage;
system( $command, $output, $retval );
} else {
Error("Can't create frame images from video becuase there is no video file for this event " );
}
}
}
$captPath = $eventPath.'/'.$captImage;
$thumbCaptPath = ZM_DIR_IMAGES.'/'.$event['Id'].'-'.$captImage;
//echo "CI:$captImage, CP:$captPath, TCP:$thumbCaptPath<br>";
$analImage = sprintf( "%0".ZM_EVENT_IMAGE_DIGITS."d-analyse.jpg", $frame['FrameId'] );
$analPath = $eventPath.'/'.$analImage;
$analFile = ZM_DIR_EVENTS."/".$analPath;
$thumbAnalPath = ZM_DIR_IMAGES.'/'.$event['Id'].'-'.$analImage;
//echo "AI:$analImage, AP:$analPath, TAP:$thumbAnalPath<br>";
$alarmFrame = $frame['Type']=='Alarm';
$hasAnalImage = $alarmFrame && file_exists( $analFile ) && filesize( $analFile );
$hasAnalImage = $alarmFrame && file_exists( $analPath ) && filesize( $analPath );
$isAnalImage = $hasAnalImage && !$captureOnly;
if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) )
@ -1207,22 +1215,20 @@ function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $ov
$thumbPath = $thumbCaptPath;
}
$imageFile = ZM_DIR_EVENTS."/".$imagePath;
//$thumbFile = ZM_DIR_EVENTS."/".$thumbPath;
$thumbFile = $thumbPath;
if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) )
{
// Get new dimensions
list( $imageWidth, $imageHeight ) = getimagesize( $imageFile );
list( $imageWidth, $imageHeight ) = getimagesize( $imagePath );
$thumbWidth = $imageWidth * $fraction;
$thumbHeight = $imageHeight * $fraction;
// Resample
$thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight );
$image = imagecreatefromjpeg( $imageFile );
$image = imagecreatefromjpeg( $imagePath );
imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight );
if ( !imagejpeg( $thumbImage, $thumbFile ) )
if ( !imagejpeg( $thumbImage, $thumbPath ) )
Error( "Can't create thumbnail '$thumbPath'" );
}
}
@ -1231,15 +1237,13 @@ function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $ov
'eventPath' => $eventPath,
'imagePath' => $imagePath,
'thumbPath' => $thumbPath,
'imageFile' => $imageFile,
'imageFile' => $imagePath,
'thumbFile' => $thumbFile,
'imageClass' => $alarmFrame?"alarm":"normal",
'isAnalImage' => $isAnalImage,
'hasAnalImage' => $hasAnalImage,
);
//echo "IP:$imagePath<br>";
//echo "TP:$thumbPath<br>";
return( $imageData );
}

View File

@ -584,6 +584,7 @@ $SLANG = array(
'Protocol' => 'Protocol',
'Rate' => 'Rate',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP
'RecordAudio' => 'Whether to store the audio stream when saving an event.',
'Real' => 'Real',
'Record' => 'Record',
'RefImageBlendPct' => 'Reference Image Blend %ge',

View File

@ -55,7 +55,7 @@ $replayModes = array(
if ( isset( $_REQUEST['streamMode'] ) )
$streamMode = validHtmlStr($_REQUEST['streamMode']);
else
$streamMode = video;
$streamMode = 'video';
if ( isset( $_REQUEST['replayMode'] ) )
$replayMode = validHtmlStr($_REQUEST['replayMode']);

View File

@ -86,6 +86,7 @@ if ( ! empty($_REQUEST['mid']) ) {
'SaveJPEGs' => "3",
'VideoWriter' => "0",
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
'RecordAudio' => "0",
'LabelFormat' => '%N - %d/%m/%y %H:%M:%S',
'LabelX' => 0,
'LabelY' => 0,
@ -593,6 +594,7 @@ if ( $tab != 'storage' )
<input type="hidden" name="newMonitor[SaveJPEGs]" value="<?php echo validHtmlStr($newMonitor['SaveJPEGs']) ?>"/>
<input type="hidden" name="newMonitor[VideoWriter]" value="<?php echo validHtmlStr($newMonitor['VideoWriter']) ?>"/>
<input type="hidden" name="newMonitor[EncoderParameters]" value="<?php echo validHtmlStr($newMonitor['EncoderParameters']) ?>"/>
<input type="hidden" name="newMonitor[RecordAudio]" value="<?php echo validHtmlStr($newMonitor['RecordAudio']) ?>"/>
<?php
}
if ( $tab != 'source' || ($newMonitor['Type'] != 'Remote' && $newMonitor['Protocol'] != 'RTSP'))
@ -891,6 +893,7 @@ switch ( $tab )
<tr><td><?php echo translate('SaveJPEGs') ?></td><td><select name="newMonitor[SaveJPEGs]"><?php foreach ( $savejpegopts as $name => $value ) { ?><option value="<?php echo $value ?>"<?php if ( $value == $newMonitor['SaveJPEGs'] ) { ?> selected="selected"<?php } ?>><?php echo $name ?></option><?php } ?></select></td></tr>
<tr><td><?php echo translate('VideoWriter') ?></td><td><select name="newMonitor[VideoWriter]"><?php foreach ( $videowriteropts as $name => $value ) { ?><option value="<?php echo $value ?>"<?php if ( $value == $newMonitor['VideoWriter'] ) { ?> selected="selected"<?php } ?>><?php echo $name ?></option><?php } ?></select></td></tr>
<tr><td><?php echo translate('OptionalEncoderParam') ?></td><td><textarea name="newMonitor[EncoderParameters]" rows="4" cols="36"><?php echo validHtmlStr($newMonitor['EncoderParameters']) ?></textarea></td></tr>
<tr><td><?php echo translate('RecordAudio') ?></td><td><input type="checkbox" name="newMonitor[RecordAudio]" value="1"<?php if ( !empty($newMonitor['RecordAudio']) ) { ?> checked="checked"<?php } ?>/></td></tr>
<?php
break;
case 'timestamp' :