Merge branch 'storageareas' into thumbnail_on_console
Conflicts: db/zm_update-1.29.1.sql docs/installationguide/index.rst src/Makefile.am src/zm_event.cpp src/zm_event.h src/zm_monitor.cpp src/zm_monitor.h src/zm_zone.cpp src/zmf.cpp web/skins/classic/views/console.php web/skins/classic/views/monitor.php
This commit is contained in:
commit
16953c6e2b
|
@ -45,4 +45,4 @@ script:
|
|||
- mysql -uzmuser -pzmpass < db/zm_create.sql
|
||||
- mysql -uzmuser -pzmpass zm < db/test.monitor.sql
|
||||
- sudo zmpkg.pl start
|
||||
- sudo zmfilter.pl -f purgewhenfull
|
||||
- sudo zmfilter.pl --filter purgewhenfull
|
||||
|
|
|
@ -401,6 +401,59 @@ else(MYSQLCLIENT_LIBRARIES)
|
|||
"ZoneMinder requires mysqlclient but it was not found on your system")
|
||||
endif(MYSQLCLIENT_LIBRARIES)
|
||||
|
||||
# x264 (using find_library and find_path)
|
||||
find_library(X264_LIBRARIES x264)
|
||||
if(X264_LIBRARIES)
|
||||
set(HAVE_LIBX264 1)
|
||||
list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}")
|
||||
find_path(X264_INCLUDE_DIR x264.h)
|
||||
if(X264_INCLUDE_DIR)
|
||||
include_directories("${X264_INCLUDE_DIR}")
|
||||
set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}")
|
||||
endif(X264_INCLUDE_DIR)
|
||||
mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR)
|
||||
check_include_files("stdint.h;x264.h" HAVE_X264_H)
|
||||
set(optlibsfound "${optlibsfound} x264")
|
||||
else(X264_LIBRARIES)
|
||||
set(optlibsnotfound "${optlibsnotfound} x264")
|
||||
endif(X264_LIBRARIES)
|
||||
|
||||
# mp4v2 (using find_library and find_path)
|
||||
find_library(MP4V2_LIBRARIES mp4v2)
|
||||
if(MP4V2_LIBRARIES)
|
||||
set(HAVE_LIBMP4V2 1)
|
||||
list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}")
|
||||
|
||||
# mp4v2/mp4v2.h
|
||||
find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h)
|
||||
if(MP4V2_INCLUDE_DIR)
|
||||
include_directories("${MP4V2_INCLUDE_DIR}")
|
||||
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
|
||||
endif(MP4V2_INCLUDE_DIR)
|
||||
check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H)
|
||||
|
||||
# mp4v2.h
|
||||
find_path(MP4V2_INCLUDE_DIR mp4v2.h)
|
||||
if(MP4V2_INCLUDE_DIR)
|
||||
include_directories("${MP4V2_INCLUDE_DIR}")
|
||||
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
|
||||
endif(MP4V2_INCLUDE_DIR)
|
||||
check_include_file("mp4v2.h" HAVE_MP4V2_H)
|
||||
|
||||
# mp4.h
|
||||
find_path(MP4V2_INCLUDE_DIR mp4.h)
|
||||
if(MP4V2_INCLUDE_DIR)
|
||||
include_directories("${MP4V2_INCLUDE_DIR}")
|
||||
set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}")
|
||||
endif(MP4V2_INCLUDE_DIR)
|
||||
check_include_file("mp4.h" HAVE_MP4_H)
|
||||
|
||||
mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR)
|
||||
set(optlibsfound "${optlibsfound} mp4v2")
|
||||
else(MP4V2_LIBRARIES)
|
||||
set(optlibsnotfound "${optlibsnotfound} mp4v2")
|
||||
endif(MP4V2_LIBRARIES)
|
||||
|
||||
set(PATH_FFMPEG "")
|
||||
set(OPT_FFMPEG "no")
|
||||
# Do not check for ffmpeg if ZM_NO_FFMPEG is on
|
||||
|
|
18
README.md
18
README.md
|
@ -1,7 +1,19 @@
|
|||
ZoneMinder
|
||||
==========
|
||||
ZoneMinder H264 Patch
|
||||
|
||||
[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png)](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)
|
||||
[![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 provides h264 encoding for any other monitor type. 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.**
|
||||
```
|
||||
ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing` ,
|
||||
ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs` ,
|
||||
ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter` ;
|
||||
|
||||
ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames` ;
|
||||
```
|
||||
|
||||
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
@ -346,6 +347,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',
|
||||
|
@ -579,7 +584,7 @@ insert into Users VALUES (NULL,'admin',password('admin'),'',1,'View','Edit','Edi
|
|||
--
|
||||
-- Add a sample filter to purge the oldest 100 events when the disk is 95% full
|
||||
--
|
||||
insert into Filters values ('PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,1);
|
||||
insert into Filters values ('PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,1,0);
|
||||
|
||||
--
|
||||
-- Add in some sample control protocol definitions
|
||||
|
|
|
@ -1,7 +1,64 @@
|
|||
--
|
||||
-- This updates a 1.29.0 database to 1.29.1
|
||||
--
|
||||
<<<<<<< HEAD
|
||||
--
|
||||
=======
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_name = 'Storage'
|
||||
AND table_schema = DATABASE()
|
||||
) > 0,
|
||||
"SELECT 'Storage table exists'",
|
||||
"CREATE TABLE `Storage` (
|
||||
`Id` smallint(5) unsigned NOT NULL auto_increment,
|
||||
`Path` varchar(64) NOT NULL default '',
|
||||
`Name` varchar(64) NOT NULL default '',
|
||||
PRIMARY KEY (`Id`)
|
||||
)"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add StorageId column to Monitors
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'StorageId'
|
||||
) > 0,
|
||||
"SELECT 'Column StorageId exists in Monitors'",
|
||||
"ALTER TABLE Monitors ADD `StorageId` smallint(5) unsigned AFTER `ServerId`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add StorageId column to Eventss
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Events'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'StorageId'
|
||||
) > 0,
|
||||
"SELECT 'Column StorageId exists in Events'",
|
||||
"ALTER TABLE Events ADD `StorageId` smallint(5) unsigned AFTER `MonitorId`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
>>>>>>> storageareas
|
||||
|
||||
-- Increase the size of the Pid field for FreeBSD
|
||||
ALTER TABLE Logs MODIFY Pid int(10);
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
--
|
||||
-- This updates a 1.29.0 database to 1.30.0
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'SaveJPEGs'
|
||||
) > 0,
|
||||
"SELECT 'Column SaveJPEGs exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'VideoWriter'
|
||||
) > 0,
|
||||
"SELECT 'Column VideoWriter exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'EncoderParameters'
|
||||
) > 0,
|
||||
"SELECT 'Column EncoderParameters exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Events'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'DefaultVideo'
|
||||
) > 0,
|
||||
"SELECT 'Column DefaultVideo exists in Events'",
|
||||
"ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'RecordAudio'
|
||||
) > 0,
|
||||
"SELECT 'Column RecordAudio exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -0,0 +1,73 @@
|
|||
--
|
||||
-- This updates a 1.29.0 database to 1.30.0
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'SaveJPEGs'
|
||||
) > 0,
|
||||
"SELECT 'Column SaveJPEGs exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'VideoWriter'
|
||||
) > 0,
|
||||
"SELECT 'Column VideoWriter exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'EncoderParameters'
|
||||
) > 0,
|
||||
"SELECT 'Column EncoderParameters exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Events'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'DefaultVideo'
|
||||
) > 0,
|
||||
"SELECT 'Column DefaultVideo exists in Events'",
|
||||
"ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'RecordAudio'
|
||||
) > 0,
|
||||
"SELECT 'Column RecordAudio exists in Monitors'",
|
||||
"ALTER TABLE `Monitors` ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -1,3 +1,21 @@
|
|||
zoneminder (1.30.2-trusty-2016050201) trusty; urgency=medium
|
||||
|
||||
* add Servers support to API
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Mon, 02 May 2016 10:45:24 -0400
|
||||
|
||||
zoneminder (1.30.2-trusty-2016042901) trusty; urgency=medium
|
||||
|
||||
* zone edit fixes
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Fri, 29 Apr 2016 10:46:18 -0400
|
||||
|
||||
zoneminder (1.30.2-trusty-2016042801) trusty; urgency=medium
|
||||
|
||||
* Merge video
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Thu, 28 Apr 2016 12:54:08 -0400
|
||||
|
||||
zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium
|
||||
|
||||
* include api, switch to cmake build
|
||||
|
|
|
@ -1,3 +1,39 @@
|
|||
zoneminder (1.30.2-trusty-2016033001) trusty; urgency=medium
|
||||
|
||||
* merge master
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Wed, 30 Mar 2016 14:09:48 -0400
|
||||
|
||||
zoneminder (1.30.2-trusty-2016032901) trusty; urgency=medium
|
||||
|
||||
* filter fixes, merge options rework by Kyle
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Tue, 29 Mar 2016 12:27:57 -0400
|
||||
|
||||
zoneminder (1.30.2-trusty-2016030702) trusty; urgency=medium
|
||||
|
||||
*
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Mon, 07 Mar 2016 22:14:03 -0500
|
||||
|
||||
zoneminder (1.30.2-trusty-2016030701) trusty; urgency=medium
|
||||
|
||||
* merge master. with telemetry
|
||||
|
||||
-- Isaac Connor <iconnor@connortechnology.com> Mon, 07 Mar 2016 21:47:53 -0500
|
||||
|
||||
zoneminder (1.30.2-trusty-2016022101) trusty; urgency=medium
|
||||
|
||||
* merge zmtrigger fix
|
||||
|
||||
-- Isaac Connor <iconnor@testing.internal.point-one.com> Mon, 22 Feb 2016 09:15:53 -0500
|
||||
|
||||
zoneminder (1.30.2-trusty-2016021901) trusty; urgency=medium
|
||||
|
||||
* zmtrigger improvements
|
||||
|
||||
-- Isaac Connor <iconnor@testing.internal.point-one.com> Fri, 19 Feb 2016 11:09:57 -0500
|
||||
|
||||
zoneminder (1.30.2-trusty-2016021701) trusty; urgency=medium
|
||||
|
||||
* printout id, and ip address when failing to connect
|
||||
|
|
|
@ -53,7 +53,7 @@ override_dh_auto_configure:
|
|||
--with-mariadb=/usr --with-webdir=/usr/share/zoneminder \
|
||||
--with-ffmpeg=/usr --with-cgidir=/usr/lib/cgi-bin \
|
||||
--with-webuser=www-data --with-webgroup=www-data \
|
||||
--enable-crashtrace=no --enable-mmap=yes $(DEBOPT)
|
||||
--enable-mmap=yes $(DEBOPT)
|
||||
|
||||
override_dh_clean:
|
||||
# Add here commands to clean up after the build process.
|
||||
|
|
|
@ -6,6 +6,7 @@ Uploaders: Vagrant Cascadian <vagrant@debian.org>
|
|||
Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree
|
||||
,cmake
|
||||
,libavcodec-ffmpeg-dev, libavformat-ffmpeg-dev, libswscale-ffmpeg-dev, libavutil-ffmpeg-dev, libavdevice-ffmpeg-dev
|
||||
,libx264-dev, libmp4v2-dev,
|
||||
,libbz2-dev
|
||||
,libgcrypt-dev
|
||||
,libcurl4-gnutls-dev
|
||||
|
@ -34,10 +35,8 @@ Architecture: any
|
|||
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||
,javascript-common
|
||||
,libav-tools
|
||||
,libdate-manip-perl
|
||||
,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl
|
||||
,libdbd-mysql-perl
|
||||
,libmime-lite-perl
|
||||
,libmime-tools-perl
|
||||
,libphp-serialization-perl
|
||||
,libmodule-load-conditional-perl
|
||||
,libnet-sftp-foreign-perl
|
||||
|
@ -57,12 +56,12 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
|||
,libsys-cpu-perl, libsys-meminfo-perl
|
||||
,mysql-client | virtual-mysql-client
|
||||
,perl-modules
|
||||
,php5-mysql, php5-gd
|
||||
,php5-mysql | php-mysql, php5-gd | php-gd
|
||||
,policykit-1
|
||||
,rsyslog | system-log-daemon
|
||||
,zip
|
||||
Recommends: ${misc:Recommends}
|
||||
,libapache2-mod-php5 | php5-fpm
|
||||
,libapache2-mod-php5 | libapache2-mod-php7 | php5-fpm
|
||||
,mysql-server | virtual-mysql-server
|
||||
,zoneminder-doc (>= ${source:Version})
|
||||
Suggests: fcgiwrap, logrotate
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
Debian
|
||||
======
|
||||
|
||||
A fresh build based on master branch running Debian 7 (wheezy)\:
|
||||
::
|
||||
|
||||
root@host:~# aptitude install -y apache2 mysql-server php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libpcre3 libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf libjpeg8-dev libjpeg8 apache2-mpm-prefork libapache2-mod-php5 php5-cli libphp-serialization-perl libgnutls-dev libjpeg8-dev libavcodec-dev libavformat-dev libswscale-dev libavutil-dev libv4l-dev libtool ffmpeg libnetpbm10-dev libavdevice-dev libmime-lite-perl dh-autoreconf dpatch;
|
||||
|
||||
root@host:~# git clone https://github.com/ZoneMinder/ZoneMinder.git zoneminder;
|
||||
root@host:~# cd zoneminder;
|
||||
root@host:~# ln -s distros/debian;
|
||||
root@host:~# dpkg-checkbuilddeps;
|
||||
root@host:~# dpkg-buildpackage;
|
||||
|
||||
One level above you'll now find a deb package matching the architecture of the build host:
|
||||
::
|
||||
|
||||
root@host:~# ls -1 ~/zoneminder*;
|
||||
/root/zoneminder_1.26.4-1_amd64.changes
|
||||
/root/zoneminder_1.26.4-1_amd64.deb
|
||||
/root/zoneminder_1.26.4-1.dsc
|
||||
/root/zoneminder_1.26.4-1.tar.gz
|
||||
|
||||
The dpkg command itself does not resolve dependencies. That's what high-level interfaces like aptitude and apt-get are normally for. Unfortunately, unlike RPM, there's no easy way to install a separate deb package not contained with any repository.
|
||||
|
||||
To overcome this "limitation" we'll use dpkg only to install the zoneminder package and apt-get to fetch all needed dependencies afterwards. Running dpkg-reconfigure in the end will ensure that the setup scripts e.g. for database provisioning were executed.
|
||||
::
|
||||
|
||||
root@host:~# dpkg -i /root/zoneminder_1.26.4-1_amd64.deb; apt-get install -f;
|
||||
root@host:~# dpkg-reconfigure zoneminder;
|
||||
|
||||
Alternatively you may also use gdebi to automatically resolve dependencies during installation:
|
||||
::
|
||||
|
||||
root@host:~# aptitude install -y gdebi;
|
||||
root@host:~# gdebi /root/zoneminder_1.26.4-1_amd64.deb;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
|
@ -1 +0,0 @@
|
|||
<mxfile type="dropbox" userAgent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0" version="5.2.7.3" editor="www.draw.io"><diagram>7Vvfb+I4F/1rkHYfFpEEQnlsmXZ2pXZUqaPd/fZlZIib+JsQs44pZf76uU6uEydOgELoUgkeELn+fc/JuddO6HnTxetnQZbRAw9o3HMHwWvP+9Rz3SvfhW9l2OSGsTfKDaFgQW5ySsMT+0HROEDrigU0rVSUnMeSLavGOU8SOpcVGxGCr6vVnnlcHXVJQj1iaXiak9i2/sUCGeGyXL+0/05ZGOmRHX+Sl8zI/Hso+CrB8Xqu95x98uIF0X1lC/VuwYeCc+hG/Vq8Tmms/Kh9lHvjrqW0mKSgCU5kRwMHUXgh8QpX+g9P6ANLAirA/rACF//2RMVLdvmFyjUX3+HXV77kMQ83PdePYaibGRT7ofqVd5jKjXZdtniqRnSgeB0xSZ+WZK5K18AVsEVyEWNxKgX/TqfQtwBLAnMB4zOL45op4oL94IkkuiFMUDLA6zpmYQI2yVXPz1AFyeRcFZMzHYQ+U80pkjUzocM+U76gUsA6B1jqISORyB52sC5ZMR5jlchgxFgzmSATw6LnEiD4gRi14OWOLbweSALkW6gF7cYC1qjsBzicoF/nMBBwwXb4ggWBGqYRYQ61n2O+BksE9Sg0MLFR0AguiWRcdeWCA2Fm0AVLQnVdXgHvwKCKVXM9R7ipBgPfn067gdgZDCsYu+MrC+QR3jkmxg7e88dBjJ0YEN+zBO65C7jdgKtRKsAd9xHLHfCimB4Fr2aSARsNINLgJRcy4iFPSHxbWsF7WkGVbwx8aRJcq+BWIgmWO8AXq/6fSrlBGMhKcgVWMcI9z9yt+ml1a8pXIoM6cxzGcElESLHeCAVJLWKr9wWNgQEv1XDa5MmsKayKqFa6wpKzRKZGz4/KYIhyTZUdDMwlKnmPza2dQa31EMO6Hj53A7aqgV2scS/8J+jDjvB/ZfJvZe57Pl7+L7scT/DykQoG01M3ddb4XRnjoYyeIWFGflXia4Sx6g8neOPuQ7ADeKHdYqj+n5DxcjDdX3+xOGNwwJRzkMpp9rG0H0r8qyvnxoOSUJCAAW61IKDNn5iADDoX60SBX3SmM99MsiOyVJNZvEK7ZdRP8tQw7c9WAMEhY9Q0H7Nk72ZpUFhl0zPoyqD1jbYha0GlMfyguxqy4jcHDb8qEOBMO+vTCV7XIWOEzDOo8SS5UBuWCznOkBzDyTuSowjKtnDk+WLAXnTCOI2Vd6HuL183S5Xd/dqUVOYWGNdoaVGsLT9r3oVp6z2Z0fiRpwzBnXEp+QIqxLWCIg+1EtMz5vZyfgJuOTVyTeztZpG9VLabnXALifzfJSswY7OJujbbqOt6o5NnMNrWXQazPyD23tBGyMrwUpi9rNnM+6h2RpJVN1LCaoLY6rad7jB3Vw2M1ba35X12Lj9sUeMdufzOjootoe4o50QXmwJfy8IWWNUZYnGihWac4D4CU5yMkpnusTxwbBKeSTU/9jT1TOFpCmqF8aiUp4nneWBS689Wo+Xd/3eljkuBmN7dnQcf05SHrzJd0ieZW8JePsAl7u0d99Lcp7VTHKXXO4JhQfvd0dBg3aSJdDoEHkM6HWm33YUGDPuBe1Mc/zdlr820afB9K7StZIhIkOm90uyApFERkes4Mp6O+wzUIe2rpyIZkkdhpbPi6oZ5bCfFQ/SviaQ+xznuCHUPPT3bMInuc8cYborwYztwWxjdP2LWRxpO8CzirRGzdcrdR0zd8xsDxGAwnSqpqAeITwTiIkntCGHFA2tTdcywb4hDl13ZUdHpW4opQPc7NHgKXM2U9La+skVrelqkjcdpnb3//3BaN3QgaWtKtTvXuvpIB+8OWqd8gt3B9kf0WrAuWrB/ptq1Bng+hsxCA2wCnyxvdVyk4keWgNGwnjucSgLqIx0sAa1T7l4C7IdDFwk4MwkYTqqvFbyzBOBpzUeWAP/ddjz1kQ7e8bROuXsJsB8CXiTgzCTAr78a+J4SoJ9KNOyIZ3rD+McjVJiSBRXE3nru91Cw6OtCtb2oNl8JJjff5rnTO3mBbTSqbj2KR8AGzTQTO3/crDu+vIB6yncUXa/+jqL99OVkL6DqN5ovL6CeCNyR9Xbx6V5Ahcvy7wR52lH+P8O7/Qk=</diagram></mxfile>
|
|
@ -1,55 +0,0 @@
|
|||
Multi-Server Install
|
||||
====================
|
||||
|
||||
It is possible to run multiple ZoneMinder servers and manage them from a single interface. To achieve this each zoneminder server is connected to a single shared database server and shares file storage for event data.
|
||||
|
||||
.. image:: images/zm-multiserver.png
|
||||
|
||||
Topology Design Notes
|
||||
---------------------
|
||||
|
||||
1. Device symbols represent separate logical functions, not necessarily separate hardware. For example, the Database Server and a ZoneMinder Server, can reside on the same physical hardware.
|
||||
|
||||
2. Configure each ZoneMinder Server to use the same, remote Database Server (Green).
|
||||
|
||||
3. The Storage Server (Red) represents shared storage, accessible by all ZoneMinder Servers, mounted under each server’s events folder.
|
||||
|
||||
4. Create at least two networks for best performance. Dedicate a Storage LAN for communication with the Storage and Database Servers. Make use of multipath and jumbo frames if possible. Keep all other traffic off the Storage LAN! Dedicate the second LAN, called the Video LAN in the diagram, for all other traffic.
|
||||
|
||||
New installs
|
||||
------------
|
||||
|
||||
1. Follow the normal instructions for your distro for installing ZoneMinder onto all the ZoneMinder servers in the normal fashion. Only a single database will be needed either as standalone, or on one of the ZoneMinder Servers.
|
||||
|
||||
2. On each ZoneMinder server, edit zm.conf. Find the ZM_DB_HOST variable and set it to the name or ip address of your Database Server. Find the ZM_SERVER_HOST and enter a name for this ZoneMinder server. Use a name easily recognizable by you. This name is not used by ZoneMinder for dns or any other form of network conectivity.
|
||||
|
||||
3. Copy the file /usr/share/zoneminder/db/zm_create.sql from one of the ZoneMinder Servers to the machine targeted as the Database Server.
|
||||
|
||||
4. Install mysql/mariadb server onto the Database Server.
|
||||
|
||||
5. It is advised to run "mysql_secure_installation" to help secure the server.
|
||||
|
||||
6. Using the password for the root account set during the previous step, create the ZoneMinder database and configure a database account for ZoneMinder to use:
|
||||
|
||||
::
|
||||
|
||||
mysql -u root -p < zm_create.sql
|
||||
mysql -uroot -p -e "grant all on zm.* to 'zmuser'@localhost identified by 'zmpass';"
|
||||
mysqladmin -u root -p reload
|
||||
|
||||
The database account credentials, zmuser/zmpass, are arbitrary. Set them to anything that suits your environment.
|
||||
Note that these commands are just an example and might not be secure enough for your environment.
|
||||
|
||||
7. If you have chosen to change the ZoneMinder database account credentials to something other than zmuser/zmpass, you must now update zm.conf on each ZoneMinder Server. Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous step.
|
||||
|
||||
Additionally, you must also edit /usr/share/zoneminder/www/api/app/Config/database.php in a similar manner on each ZoneMinder Server. Scroll down and change login and password to the values you created in the previous step.
|
||||
|
||||
8. All ZoneMinders Servers must share a common events folder. This can be done in any manner supported by the underlying operating system. From the Storage Server, share/export a folder to be used for ZoneMinder events.
|
||||
|
||||
9. From each ZoneMinder Server, mount the shared events folder on the Storage Server to the events folder on the local ZoneMinder Server.
|
||||
|
||||
NOTE: The location of this folder varies by distro. This folder is often found under "/var/lib/zoneminder/events" for RedHat based distros and "/var/cache/zoneminder/events" for Debain based distros. This folder is NOT a Symbolic Link!
|
||||
|
||||
10. Open your browser and point it to the web console on any of the ZoneMinder Servers (they will all be the same). Open Options, click the Servers tab,and populate this screen with all of your ZoneMinder Servers. Each server has a field for its name and its hostname. The name is what you used for ZM_SERVER_HOST in step 2. The hostname is the network name or ip address ZoneMinder should use.
|
||||
|
||||
11. When creating a new Monitor, remember to select the server the camera will be assigned to from the Server drop down box.
|
|
@ -19,7 +19,8 @@ bin_SCRIPTS = \
|
|||
zmcontrol.pl \
|
||||
zmtrack.pl \
|
||||
zmcamtool.pl \
|
||||
zmsystemctl.pl
|
||||
zmsystemctl.pl \
|
||||
zmtelemetry.pl
|
||||
|
||||
SUBDIRS = \
|
||||
. \
|
||||
|
@ -39,6 +40,7 @@ EXTRA_DIST = \
|
|||
zmtrack.pl.in \
|
||||
zmcamtool.pl.in \
|
||||
zmsystemctl.pl.in \
|
||||
zmtelemtry.pl.in \
|
||||
ZoneMinder/Makefile.PL \
|
||||
ZoneMinder/README \
|
||||
ZoneMinder/Changes \
|
||||
|
|
|
@ -155,10 +155,12 @@ MAIN: while( $loop ) {
|
|||
Fatal("ZM_AUDIT_MIN_AGE is not set in config.");
|
||||
}
|
||||
|
||||
my %Monitors;
|
||||
my $db_monitors;
|
||||
my $monitorSelectSql = "select Id from Monitors order by Id";
|
||||
my $monitorSelectSql = "select * from Monitors order by Id";
|
||||
my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql )
|
||||
or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() );
|
||||
|
||||
my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age
|
||||
FROM Events WHERE MonitorId = ? ORDER BY Id";
|
||||
my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql )
|
||||
|
@ -169,6 +171,8 @@ MAIN: while( $loop ) {
|
|||
or Fatal( "Can't execute: ".$monitorSelectSth->errstr() );
|
||||
while( my $monitor = $monitorSelectSth->fetchrow_hashref() )
|
||||
{
|
||||
$Monitors{$$monitor{Id}} = $monitor;
|
||||
|
||||
Debug( "Found database monitor '$monitor->{Id}'" );
|
||||
my $db_events = $db_monitors->{$monitor->{Id}} = {};
|
||||
my $res = $eventSelectSth->execute( $monitor->{Id} )
|
||||
|
@ -467,20 +471,30 @@ MAIN: while( $loop ) {
|
|||
|
||||
# New audit to close any events that were left open for longer than MIN_AGE seconds
|
||||
my $selectUnclosedEventsSql =
|
||||
"SELECT E.Id,
|
||||
max(F.TimeStamp) as EndTime,
|
||||
unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length,
|
||||
max(F.FrameId) as Frames,
|
||||
#"SELECT E.Id, ANY_VALUE(E.MonitorId),
|
||||
#
|
||||
#max(F.TimeStamp) as EndTime,
|
||||
#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length,
|
||||
#max(F.FrameId) as Frames,
|
||||
#count(if(F.Score>0,1,NULL)) as AlarmFrames,
|
||||
#sum(F.Score) as TotScore,
|
||||
#max(F.Score) as MaxScore
|
||||
#FROM Events as E
|
||||
#INNER JOIN Frames as F on E.Id = F.EventId
|
||||
#WHERE isnull(E.Frames) or isnull(E.EndTime)
|
||||
#GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)"
|
||||
#;
|
||||
"SELECT *, unix_timestamp(StartTime) AS TimeStamp FROM Events WHERE EndTime IS NULL AND StartTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)";
|
||||
|
||||
my $selectFrameDataSql = "SELECT max(TimeStamp) as EndTime, unix_timestamp(max(TimeStamp)) AS EndTimeStamp, max(FrameId) as Frames,
|
||||
count(if(F.Score>0,1,NULL)) as AlarmFrames,
|
||||
sum(F.Score) as TotScore,
|
||||
max(F.Score) as MaxScore,
|
||||
M.EventPrefix as Prefix
|
||||
FROM Events as E
|
||||
LEFT JOIN Monitors as M on E.MonitorId = M.Id
|
||||
INNER JOIN Frames as F on E.Id = F.EventId
|
||||
WHERE isnull(E.Frames) or isnull(E.EndTime)
|
||||
GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)"
|
||||
;
|
||||
max(F.Score) as MaxScore
|
||||
FROM Frames WHERE EventId=?";
|
||||
my $selectFrameDataSth = $dbh->prepare_cached($selectFrameDataSql)
|
||||
or Fatal( "Can't prepare '$selectFrameDataSql': ".$dbh->errstr() );
|
||||
|
||||
|
||||
my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql )
|
||||
or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() );
|
||||
my $updateUnclosedEventsSql =
|
||||
|
@ -505,26 +519,32 @@ MAIN: while( $loop ) {
|
|||
aud_print( "Found open event '$event->{Id}'" );
|
||||
if ( confirm( 'close', 'closing' ) )
|
||||
{
|
||||
$res = $selectFrameDataSth->execute( $event->{Id} );
|
||||
my $frame = $selectFrameDataSth->fetchrow_hashref();
|
||||
if ( $frame ) {
|
||||
$res = $updateUnclosedEventsSth->execute
|
||||
(
|
||||
sprintf("%s%d%s",
|
||||
$event->{Prefix},
|
||||
$Monitors{$event->{MonitorId}}->{EventPrefix},
|
||||
$event->{Id},
|
||||
RECOVER_TAG
|
||||
),
|
||||
$event->{EndTime},
|
||||
$event->{Length},
|
||||
$event->{Frames},
|
||||
$event->{AlarmFrames},
|
||||
$event->{TotScore},
|
||||
$event->{AlarmFrames}
|
||||
? int($event->{TotScore} / $event->{AlarmFrames})
|
||||
$frame->{EndTime},
|
||||
$frame->{EndTimeStamp} - $event->{TimeStamp},
|
||||
$frame->{Frames},
|
||||
$frame->{AlarmFrames},
|
||||
$frame->{TotScore},
|
||||
$frame->{AlarmFrames}
|
||||
? int($frame->{TotScore} / $frame->{AlarmFrames})
|
||||
: 0
|
||||
,
|
||||
$event->{MaxScore},
|
||||
$frame->{MaxScore},
|
||||
RECOVER_TEXT,
|
||||
$event->{Id}
|
||||
) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() );
|
||||
} else {
|
||||
Error("SHOULD DELETE");
|
||||
} # end if has frame data
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -246,7 +246,7 @@ sub getFilters
|
|||
}
|
||||
$sth->finish();
|
||||
if ( ! @filters ) {
|
||||
Error("No filter found for $sql with values(@sql_values)");
|
||||
Warning("No filter found for $sql with values(@sql_values)");
|
||||
} else {
|
||||
Debug( "Got " . @filters . " filters" );
|
||||
}
|
||||
|
|
|
@ -198,6 +198,7 @@ my %spawned_connections;
|
|||
my %monitors;
|
||||
|
||||
my $monitor_reload_time = 0;
|
||||
my $needsReload = 0;
|
||||
|
||||
$! = undef;
|
||||
my $rin = '';
|
||||
|
@ -218,8 +219,8 @@ while( 1 )
|
|||
if ( $nfound > 0 )
|
||||
{
|
||||
Debug( "Got input from $nfound connections\n" );
|
||||
foreach my $connection ( @in_select_connections )
|
||||
{
|
||||
foreach my $connection ( @in_select_connections ) {
|
||||
|
||||
if ( vec( $rout, $connection->fileno(), 1 ) )
|
||||
{
|
||||
Debug( "Got input from connection "
|
||||
|
@ -311,8 +312,15 @@ while( 1 )
|
|||
|
||||
# Check for alarms that might have happened
|
||||
my @out_messages;
|
||||
foreach my $monitor ( values(%monitors) )
|
||||
{
|
||||
foreach my $monitor ( values(%monitors) ) {
|
||||
|
||||
my $memVerified = 1;
|
||||
if ( !zmMemRead($monitor, "shared_data:valid") ) {
|
||||
zmMemInvalidate($monitor);
|
||||
$memVerified = zmMemVerify($monitor);
|
||||
}
|
||||
|
||||
if ($memVerified) {
|
||||
my ( $state, $last_event )
|
||||
= zmMemRead( $monitor,
|
||||
[ "shared_data:state",
|
||||
|
@ -324,39 +332,36 @@ while( 1 )
|
|||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
||||
if ( $state == STATE_ALARM
|
||||
|| $state == STATE_ALERT
|
||||
) # In alarm state
|
||||
{
|
||||
) { # In alarm state
|
||||
if ( !defined($monitor->{LastEvent})
|
||||
|| ($last_event != $monitor->{LastEvent})
|
||||
) # A new event
|
||||
{
|
||||
) { # A new event
|
||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
||||
}
|
||||
else # The same one as last time, so ignore it
|
||||
{
|
||||
} else { # The same one as last time, so ignore it
|
||||
# Do nothing
|
||||
}
|
||||
}
|
||||
elsif ( ($state == STATE_IDLE
|
||||
} elsif ( ($state == STATE_IDLE
|
||||
&& $monitor->{LastState} != STATE_IDLE
|
||||
)
|
||||
|| ($state == STATE_TAPE
|
||||
&& $monitor->{LastState} != STATE_TAPE
|
||||
)
|
||||
) # Out of alarm state
|
||||
{
|
||||
) { # Out of alarm state
|
||||
push( @out_messages, $monitor->{Id}."|off|".time()."|".$last_event );
|
||||
}
|
||||
elsif ( defined($monitor->{LastEvent})
|
||||
&& ($last_event != $monitor->{LastEvent})
|
||||
) # We've missed a whole event
|
||||
{
|
||||
) { # We've missed a whole event
|
||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
||||
push( @out_messages, $monitor->{Id}."|off|".time()."|".$last_event );
|
||||
}
|
||||
$monitor->{LastState} = $state;
|
||||
$monitor->{LastEvent} = $last_event;
|
||||
} else { # Our attempt to verify the memory handle failed. We should reload the monitors.
|
||||
$needsReload = 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $connection ( @out_connections )
|
||||
{
|
||||
if ( $connection->canWrite() )
|
||||
|
@ -413,7 +418,7 @@ while( 1 )
|
|||
}
|
||||
|
||||
# If necessary reload monitors
|
||||
if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )
|
||||
if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ))
|
||||
{
|
||||
foreach my $monitor ( values(%monitors) )
|
||||
{
|
||||
|
@ -421,6 +426,7 @@ while( 1 )
|
|||
zmMemInvalidate( $monitor );
|
||||
}
|
||||
loadMonitors();
|
||||
$needsReload = 0;
|
||||
}
|
||||
}
|
||||
Info( "Trigger daemon exiting\n" );
|
||||
|
|
|
@ -258,5 +258,5 @@ if ( $concat_name ) {
|
|||
}
|
||||
#unlink $input_file_list;
|
||||
#print( STDOUT $event->{MonitorId}.'/'.$event->{Id}.'/'.$video_file."\n" );
|
||||
#print( STDOUT $video_file."\n" );
|
||||
#print( STDOUT $event_path . '/' . $video_file ."\n" );
|
||||
exit( 0 );
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
|
||||
|
||||
# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc)
|
||||
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_zone.cpp zm_storage.cpp)
|
||||
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp)
|
||||
|
||||
# A fix for cmake recompiling the source files for every target.
|
||||
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,10 +75,13 @@ public:
|
|||
|
||||
bool CanCapture() const { return( capture ); }
|
||||
|
||||
bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); }
|
||||
|
||||
virtual int PrimeCapture() { return( 0 ); }
|
||||
virtual int PreCapture()=0;
|
||||
virtual int Capture( Image &image )=0;
|
||||
virtual int PostCapture()=0;
|
||||
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory)=0;
|
||||
};
|
||||
|
||||
#endif // ZM_CAMERA_H
|
||||
|
|
|
@ -117,7 +117,8 @@ void zmLoadConfig()
|
|||
|
||||
Debug( 1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str() );
|
||||
std::string sql = stringtf("SELECT Id FROM Servers WHERE Name='%s'", staticConfig.SERVER_NAME.c_str() );
|
||||
if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) {
|
||||
zmDbRow dbrow;
|
||||
if ( dbrow.fetch( sql.c_str() ) ) {
|
||||
staticConfig.SERVER_ID = atoi(dbrow[0]);
|
||||
} else {
|
||||
Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str() );
|
||||
|
@ -128,7 +129,8 @@ void zmLoadConfig()
|
|||
Debug( 1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID );
|
||||
std::string sql = stringtf("SELECT Name FROM Servers WHERE Id='%d'", staticConfig.SERVER_ID );
|
||||
|
||||
if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) {
|
||||
zmDbRow dbrow;
|
||||
if ( dbrow.fetch( sql.c_str() ) ) {
|
||||
staticConfig.SERVER_NAME = std::string(dbrow[0]);
|
||||
} else {
|
||||
Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID );
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
||||
|
@ -311,6 +311,14 @@ int cURLCamera::PostCapture()
|
|||
return( 0 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
|
||||
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
lock();
|
||||
|
|
|
@ -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 ); }
|
||||
|
@ -79,6 +79,8 @@ public:
|
|||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory);
|
||||
|
||||
|
||||
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
||||
size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
||||
|
|
|
@ -95,19 +95,38 @@ MYSQL_RES * zmDbFetch( const char * query ) {
|
|||
return result;
|
||||
} // end MYSQL_RES * zmDbFetch( const char * query );
|
||||
|
||||
MYSQL_ROW zmDbFetchOne( const char *query ) {
|
||||
MYSQL_RES *result = zmDbFetch( query );
|
||||
int n_rows = mysql_num_rows( result );
|
||||
zmDbRow *zmDbFetchOne( const char *query ) {
|
||||
zmDbRow *row = new zmDbRow();
|
||||
if ( row->fetch( query ) ) {
|
||||
return row;
|
||||
}
|
||||
delete row;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MYSQL_RES *zmDbRow::fetch( const char *query ) {
|
||||
result_set = zmDbFetch( query );
|
||||
if ( ! result_set ) return result_set;
|
||||
|
||||
int n_rows = mysql_num_rows( result_set );
|
||||
if ( n_rows != 1 ) {
|
||||
Error( "Bogus number of lines return from query, %d returned for query %s.", n_rows, query );
|
||||
return NULL;
|
||||
mysql_free_result( result_set );
|
||||
result_set = NULL;
|
||||
return result_set;
|
||||
}
|
||||
|
||||
MYSQL_ROW dbrow = mysql_fetch_row( result );
|
||||
mysql_free_result( result );
|
||||
if ( ! dbrow ) {
|
||||
row = mysql_fetch_row( result_set );
|
||||
if ( ! row ) {
|
||||
mysql_free_result( result_set );
|
||||
result_set = NULL;
|
||||
Error("Error getting row from query %s. Error is %s", query, mysql_error( &dbconn ) );
|
||||
return NULL;
|
||||
} else {
|
||||
Debug(3, "Succes");
|
||||
}
|
||||
return dbrow;
|
||||
return result_set;
|
||||
}
|
||||
zmDbRow::~zmDbRow() {
|
||||
if ( result_set )
|
||||
mysql_free_result( result_set );
|
||||
}
|
||||
|
|
17
src/zm_db.h
17
src/zm_db.h
|
@ -22,6 +22,21 @@
|
|||
|
||||
#include <mysql/mysql.h>
|
||||
|
||||
class zmDbRow {
|
||||
private:
|
||||
MYSQL_RES *result_set;
|
||||
MYSQL_ROW row;
|
||||
public:
|
||||
zmDbRow() { result_set = NULL; row = NULL; };
|
||||
MYSQL_RES *fetch( const char *query );
|
||||
zmDbRow( MYSQL_RES *, MYSQL_ROW *row );
|
||||
~zmDbRow();
|
||||
|
||||
char *operator[](unsigned int index) const {
|
||||
return row[index];
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -33,7 +48,7 @@ void zmDbConnect();
|
|||
void zmDbClose();
|
||||
|
||||
MYSQL_RES * zmDbFetch( const char *query );
|
||||
MYSQL_ROW zmDbFetchOne( const char *query );
|
||||
zmDbRow *zmDbFetchOne( const char *query );
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
|
285
src/zm_event.cpp
285
src/zm_event.cpp
|
@ -54,17 +54,19 @@ bool Event::initialised = false;
|
|||
char Event::capture_file_format[PATH_MAX];
|
||||
char Event::analyse_file_format[PATH_MAX];
|
||||
char Event::general_file_format[PATH_MAX];
|
||||
char Event::video_file_format[PATH_MAX];
|
||||
|
||||
int Event::pre_alarm_count = 0;
|
||||
Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } };
|
||||
|
||||
Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) :
|
||||
Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) :
|
||||
monitor( p_monitor ),
|
||||
start_time( p_start_time ),
|
||||
cause( p_cause ),
|
||||
noteSetMap( p_noteSetMap )
|
||||
noteSetMap( p_noteSetMap ),
|
||||
videoEvent( p_videoEvent ),
|
||||
videowriter( NULL )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
if ( !initialised )
|
||||
Initialise();
|
||||
|
||||
|
@ -83,29 +85,10 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
|
|||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
|
||||
struct tm *stime = localtime( &start_time.tv_sec );
|
||||
snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes ) values ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s' )", monitor->Id(), storage->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str() );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
=======
|
||||
if ( !initialised )
|
||||
Initialise();
|
||||
|
||||
std::string notes;
|
||||
createNotes( notes );
|
||||
|
||||
bool untimedEvent = false;
|
||||
if ( !start_time.tv_sec )
|
||||
{
|
||||
untimedEvent = true;
|
||||
gettimeofday( &start_time, 0 );
|
||||
}
|
||||
|
||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
|
||||
struct tm *stime = localtime( &start_time.tv_sec );
|
||||
snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s' )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str() );
|
||||
snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, Videoed ) values ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d )", monitor->Id(), storage->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str(), videoEvent );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
Error( "Can't insert event: %s", mysql_error( &dbconn ) );
|
||||
Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql );
|
||||
exit( mysql_errno( &dbconn ) );
|
||||
}
|
||||
id = mysql_insert_id( &dbconn );
|
||||
|
@ -119,28 +102,6 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
|
|||
tot_score = 0;
|
||||
max_score = 0;
|
||||
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
char *path_ptr = path;
|
||||
path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", config.dir_events, monitor->Id() );
|
||||
|
||||
int dt_parts[6];
|
||||
dt_parts[0] = stime->tm_year-100;
|
||||
dt_parts[1] = stime->tm_mon+1;
|
||||
dt_parts[2] = stime->tm_mday;
|
||||
dt_parts[3] = stime->tm_hour;
|
||||
dt_parts[4] = stime->tm_min;
|
||||
dt_parts[5] = stime->tm_sec;
|
||||
|
||||
char date_path[PATH_MAX] = "";
|
||||
char time_path[PATH_MAX] = "";
|
||||
char *time_path_ptr = time_path;
|
||||
for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ )
|
||||
>>>>>>> master
|
||||
{
|
||||
path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] );
|
||||
|
||||
<<<<<<< HEAD
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
char *path_ptr = path;
|
||||
|
@ -158,12 +119,13 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
|
|||
char time_path[PATH_MAX] = "";
|
||||
char *time_path_ptr = time_path;
|
||||
for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ )
|
||||
=======
|
||||
{
|
||||
path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] );
|
||||
|
||||
struct stat statbuf;
|
||||
errno = 0;
|
||||
if ( stat( path, &statbuf ) ) {
|
||||
if ( errno == ENOENT || errno == ENOTDIR )
|
||||
>>>>>>> master
|
||||
{
|
||||
if ( mkdir( path, 0755 ) )
|
||||
{
|
||||
|
@ -192,14 +154,6 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
|
|||
}
|
||||
else
|
||||
{
|
||||
snprintf( path, sizeof(path), "%s/%d/%d", config.dir_events, monitor->Id(), id );
|
||||
|
||||
struct stat statbuf;
|
||||
errno = 0;
|
||||
stat( path, &statbuf );
|
||||
if ( errno == ENOENT || errno == ENOTDIR )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
snprintf( path, sizeof(path), "%s/%d/%d", storage->Path(), monitor->Id(), id );
|
||||
|
||||
struct stat statbuf;
|
||||
|
@ -219,22 +173,51 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string
|
|||
fclose( id_fp );
|
||||
else
|
||||
Fatal( "Can't fopen %s: %s", id_file, strerror(errno));
|
||||
=======
|
||||
if ( mkdir( path, 0755 ) )
|
||||
{
|
||||
Error( "Can't mkdir %s: %s", path, strerror(errno));
|
||||
}
|
||||
>>>>>>> master
|
||||
}
|
||||
char id_file[PATH_MAX];
|
||||
// Create empty id tag file
|
||||
snprintf( id_file, sizeof(id_file), "%s/.%d", path, id );
|
||||
if ( FILE *id_fp = fopen( id_file, "w" ) )
|
||||
fclose( id_fp );
|
||||
else
|
||||
Fatal( "Can't fopen %s: %s", id_file, strerror(errno));
|
||||
}
|
||||
last_db_frame = 0;
|
||||
|
||||
video_name[0] = 0;
|
||||
|
||||
/* Save as video */
|
||||
|
||||
if ( monitor->GetOptVideoWriter() != 0 ) {
|
||||
int nRet;
|
||||
snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" );
|
||||
snprintf( video_file, sizeof(video_file), video_file_format, path, video_name );
|
||||
snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" );
|
||||
snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name );
|
||||
|
||||
|
||||
/* X264 MP4 video writer */
|
||||
if(monitor->GetOptVideoWriter() == 1) {
|
||||
#if ZM_HAVE_VIDEOWRITER_X264MP4
|
||||
videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams());
|
||||
#else
|
||||
videowriter = NULL;
|
||||
Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)");
|
||||
#endif
|
||||
}
|
||||
|
||||
if(videowriter != NULL) {
|
||||
/* Open the video stream */
|
||||
nRet = videowriter->Open();
|
||||
if(nRet != 0) {
|
||||
Error("Failed opening video stream");
|
||||
delete videowriter;
|
||||
videowriter = NULL;
|
||||
}
|
||||
|
||||
/* Create timecodes file */
|
||||
timecodes_fd = fopen(timecodes_file, "wb");
|
||||
if(timecodes_fd == NULL) {
|
||||
Error("Failed creating timecodes file");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* No video object */
|
||||
videowriter = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Event::~Event()
|
||||
|
@ -254,12 +237,28 @@ Event::~Event()
|
|||
}
|
||||
}
|
||||
|
||||
/* Close the video file */
|
||||
if ( videowriter != NULL ) {
|
||||
int nRet;
|
||||
|
||||
nRet = videowriter->Close();
|
||||
if(nRet != 0) {
|
||||
Error("Failed closing video stream");
|
||||
}
|
||||
delete videowriter;
|
||||
videowriter = NULL;
|
||||
|
||||
/* Close the timecodes file */
|
||||
fclose(timecodes_fd);
|
||||
timecodes_fd = NULL;
|
||||
}
|
||||
|
||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
|
||||
struct DeltaTimeval delta_time;
|
||||
DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 );
|
||||
|
||||
snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id );
|
||||
snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
Error( "Can't update event: %s", mysql_error( &dbconn ) );
|
||||
|
@ -438,6 +437,40 @@ bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char
|
|||
return( true );
|
||||
}
|
||||
|
||||
bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow )
|
||||
{
|
||||
const Image* frameimg = image;
|
||||
Image ts_image;
|
||||
|
||||
/* Checking for invalid parameters */
|
||||
if ( videow == NULL ) {
|
||||
Error("NULL Video object");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the image does not contain a timestamp, add the timestamp */
|
||||
if (!config.timestamp_on_capture) {
|
||||
ts_image = *image;
|
||||
monitor->TimestampImage( &ts_image, ×tamp );
|
||||
frameimg = &ts_image;
|
||||
}
|
||||
|
||||
/* Calculate delta time */
|
||||
struct DeltaTimeval delta_time3;
|
||||
DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 );
|
||||
unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec;
|
||||
|
||||
/* Encode and write the frame */
|
||||
if(videowriter->Encode(frameimg, timeMS) != 0) {
|
||||
Error("Failed encoding video frame");
|
||||
}
|
||||
|
||||
/* Add the frame to the timecodes file */
|
||||
fprintf(timecodes_fd, "%u\n", timeMS);
|
||||
|
||||
return( true );
|
||||
}
|
||||
|
||||
void Event::updateNotes( const StringSetMap &newNoteSetMap )
|
||||
{
|
||||
bool update = false;
|
||||
|
@ -582,9 +615,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st
|
|||
|
||||
static char event_file[PATH_MAX];
|
||||
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
|
||||
|
||||
if ( monitor->GetOptSaveJPEGs() & 4) {
|
||||
//If this is the first frame, we should add a thumbnail to the event directory
|
||||
if(frames == 10){
|
||||
char snapshot_file[PATH_MAX];
|
||||
snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path );
|
||||
WriteFrameImage( images[i], *(timestamps[i]), snapshot_file );
|
||||
}
|
||||
}
|
||||
if ( monitor->GetOptSaveJPEGs() & 1) {
|
||||
Debug( 1, "Writing pre-capture frame %d", frames );
|
||||
WriteFrameImage( images[i], *(timestamps[i]), event_file );
|
||||
}
|
||||
if ( videowriter != NULL ) {
|
||||
WriteFrameVideo( images[i], *(timestamps[i]), videowriter );
|
||||
}
|
||||
|
||||
struct DeltaTimeval delta_time;
|
||||
DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 );
|
||||
|
@ -625,8 +670,21 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
|
|||
static char event_file[PATH_MAX];
|
||||
snprintf( event_file, sizeof(event_file), capture_file_format, path, frames );
|
||||
|
||||
if ( monitor->GetOptSaveJPEGs() & 4) {
|
||||
//If this is the first frame, we should add a thumbnail to the event directory
|
||||
if(frames == 10){
|
||||
char snapshot_file[PATH_MAX];
|
||||
snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path );
|
||||
WriteFrameImage( image, timestamp, snapshot_file );
|
||||
}
|
||||
}
|
||||
if( monitor->GetOptSaveJPEGs() & 1) {
|
||||
Debug( 1, "Writing capture frame %d", frames );
|
||||
WriteFrameImage( image, timestamp, event_file );
|
||||
}
|
||||
if ( videowriter != NULL ) {
|
||||
WriteFrameVideo( image, timestamp, videowriter );
|
||||
}
|
||||
|
||||
struct DeltaTimeval delta_time;
|
||||
DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 );
|
||||
|
@ -677,9 +735,11 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
|
|||
snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames );
|
||||
|
||||
Debug( 1, "Writing analysis frame %d", frames );
|
||||
if ( monitor->GetOptSaveJPEGs() & 2) {
|
||||
WriteFrameImage( alarm_image, timestamp, event_file, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This makes viewing the diagnostic images impossible because it keeps deleting them
|
||||
if ( config.record_diag_images )
|
||||
|
@ -801,7 +861,7 @@ bool EventStream::loadEventData( int event_id )
|
|||
{
|
||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
|
||||
snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id );
|
||||
snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.StorageId, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration,E.DefaultVideo from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id );
|
||||
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
|
@ -833,24 +893,30 @@ bool EventStream::loadEventData( int event_id )
|
|||
event_data = new EventData;
|
||||
event_data->event_id = event_id;
|
||||
event_data->monitor_id = atoi( dbrow[0] );
|
||||
event_data->start_time = atoi(dbrow[3]);
|
||||
event_data->storage_id = dbrow[2] ? atoi( dbrow[2] ) : 0;
|
||||
event_data->start_time = atoi(dbrow[4]);
|
||||
|
||||
Storage * storage = new Storage( event_data->storage_id );
|
||||
const char *storage_path = storage->Path();
|
||||
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
struct tm *event_time = localtime( &event_data->start_time );
|
||||
if ( config.dir_events[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
if ( storage_path[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
else
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( config.dir_events[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", config.dir_events, event_data->monitor_id, event_data->event_id );
|
||||
if ( storage_path[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", storage_path, event_data->monitor_id, event_data->event_id );
|
||||
else
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_data->event_id );
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id );
|
||||
}
|
||||
event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]);
|
||||
event_data->duration = atof(dbrow[4]);
|
||||
event_data->frame_count = dbrow[3] == NULL ? 0 : atoi(dbrow[3]);
|
||||
event_data->duration = atof(dbrow[5]);
|
||||
strncpy( event_data->video_file, dbrow[6], sizeof( event_data->video_file )-1 );
|
||||
|
||||
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
||||
|
||||
|
@ -928,11 +994,6 @@ bool EventStream::loadEventData( int event_id )
|
|||
|
||||
void EventStream::processCommand( const CmdMsg *msg )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
|
||||
snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.StorageId, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id );
|
||||
=======
|
||||
Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] )
|
||||
// Check for incoming command
|
||||
switch( (MsgCommand)msg->msg_data[0] )
|
||||
|
@ -940,7 +1001,6 @@ void EventStream::processCommand( const CmdMsg *msg )
|
|||
case CMD_PAUSE :
|
||||
{
|
||||
Debug( 1, "Got PAUSE command" );
|
||||
>>>>>>> master
|
||||
|
||||
// Set paused flag
|
||||
paused = true;
|
||||
|
@ -1024,26 +1084,6 @@ void EventStream::processCommand( const CmdMsg *msg )
|
|||
step = 1;
|
||||
break;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
delete event_data;
|
||||
event_data = new EventData;
|
||||
event_data->event_id = event_id;
|
||||
event_data->monitor_id = atoi( dbrow[0] );
|
||||
event_data->storage_id = dbrow[2] ? atoi( dbrow[2] ) : 0;
|
||||
event_data->start_time = atoi(dbrow[4]);
|
||||
|
||||
Storage * storage = new Storage( event_data->storage_id );
|
||||
const char *storage_path = storage->Path();
|
||||
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
struct tm *event_time = localtime( &event_data->start_time );
|
||||
if ( storage_path[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
else
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec );
|
||||
=======
|
||||
case CMD_SLOWREV :
|
||||
{
|
||||
Debug( 1, "Got SLOW REV command" );
|
||||
|
@ -1054,26 +1094,9 @@ void EventStream::processCommand( const CmdMsg *msg )
|
|||
// Set step
|
||||
step = -1;
|
||||
break;
|
||||
>>>>>>> master
|
||||
}
|
||||
case CMD_FASTREV :
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
if ( storage_path[0] == '/' )
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", storage_path, event_data->monitor_id, event_data->event_id );
|
||||
else
|
||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id );
|
||||
}
|
||||
event_data->frame_count = dbrow[3] == NULL ? 0 : atoi(dbrow[3]);
|
||||
event_data->duration = atof(dbrow[5]);
|
||||
|
||||
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
||||
|
||||
mysql_free_result( result );
|
||||
|
||||
snprintf( sql, sizeof(sql), "select FrameId, unix_timestamp( `TimeStamp` ), Delta from Frames where EventId = %d order by FrameId asc", event_id );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
=======
|
||||
Debug( 1, "Got FAST REV command" );
|
||||
if ( paused )
|
||||
{
|
||||
|
@ -1103,7 +1126,6 @@ void EventStream::processCommand( const CmdMsg *msg )
|
|||
break;
|
||||
}
|
||||
case CMD_ZOOMIN :
|
||||
>>>>>>> master
|
||||
{
|
||||
x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2];
|
||||
y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4];
|
||||
|
@ -1326,6 +1348,17 @@ void EventStream::checkEventLoaded()
|
|||
}
|
||||
}
|
||||
|
||||
Image * EventStream::getImage( ) {
|
||||
Event::Initialise();
|
||||
static char filepath[PATH_MAX];
|
||||
|
||||
Debug( 2, "EventStream::getImage path(%s) frame(%d)", event_data->path, curr_frame_id );
|
||||
snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id );
|
||||
Debug( 2, "EventStream::getImage path(%s) ", filepath, curr_frame_id );
|
||||
Image *image = new Image( filepath );
|
||||
return image;
|
||||
}
|
||||
|
||||
bool EventStream::sendFrame( int delta_us )
|
||||
{
|
||||
Debug( 2, "Sending frame %d", curr_frame_id );
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "zm.h"
|
||||
#include "zm_image.h"
|
||||
#include "zm_stream.h"
|
||||
#include "zm_video.h"
|
||||
|
||||
class Zone;
|
||||
class Monitor;
|
||||
|
@ -48,22 +49,23 @@ class Monitor;
|
|||
//
|
||||
class Event
|
||||
{
|
||||
friend class EventStream;
|
||||
friend class EventStream;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static bool initialised;
|
||||
static char capture_file_format[PATH_MAX];
|
||||
static char analyse_file_format[PATH_MAX];
|
||||
static char general_file_format[PATH_MAX];
|
||||
static char video_file_format[PATH_MAX];
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static int sd;
|
||||
|
||||
public:
|
||||
public:
|
||||
typedef std::set<std::string> StringSet;
|
||||
typedef std::map<std::string,StringSet> StringSetMap;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
typedef enum { NORMAL, BULK, ALARM } FrameType;
|
||||
|
||||
struct PreAlarmData
|
||||
|
@ -77,23 +79,30 @@ protected:
|
|||
static int pre_alarm_count;
|
||||
static PreAlarmData pre_alarm_data[MAX_PRE_ALARM_FRAMES];
|
||||
|
||||
protected:
|
||||
protected:
|
||||
unsigned int id;
|
||||
Monitor *monitor;
|
||||
struct timeval start_time;
|
||||
struct timeval end_time;
|
||||
std::string cause;
|
||||
StringSetMap noteSetMap;
|
||||
bool videoEvent;
|
||||
int frames;
|
||||
int alarm_frames;
|
||||
unsigned int tot_score;
|
||||
unsigned int max_score;
|
||||
char path[PATH_MAX];
|
||||
VideoWriter* videowriter;
|
||||
FILE* timecodes_fd;
|
||||
char video_name[PATH_MAX];
|
||||
char video_file[PATH_MAX];
|
||||
char timecodes_name[PATH_MAX];
|
||||
char timecodes_file[PATH_MAX];
|
||||
|
||||
protected:
|
||||
protected:
|
||||
int last_db_frame;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static void Initialise()
|
||||
{
|
||||
if ( initialised )
|
||||
|
@ -102,18 +111,19 @@ protected:
|
|||
snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits );
|
||||
snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits );
|
||||
snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits );
|
||||
snprintf( video_file_format, sizeof(video_file_format), "%%s/%%s");
|
||||
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
void createNotes( std::string ¬es );
|
||||
|
||||
public:
|
||||
public:
|
||||
static bool OpenFrameSocket( int );
|
||||
static bool ValidateFrameSocket( int );
|
||||
|
||||
public:
|
||||
Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap );
|
||||
public:
|
||||
Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false );
|
||||
~Event();
|
||||
|
||||
int Id() const { return( id ); }
|
||||
|
@ -127,16 +137,17 @@ public:
|
|||
|
||||
bool SendFrameImage( const Image *image, bool alarm_frame=false );
|
||||
bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false );
|
||||
bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow );
|
||||
|
||||
void updateNotes( const StringSetMap &stringSetMap );
|
||||
|
||||
void AddFrames( int n_frames, Image **images, struct timeval **timestamps );
|
||||
void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL );
|
||||
|
||||
private:
|
||||
private:
|
||||
void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps );
|
||||
|
||||
public:
|
||||
public:
|
||||
static const char *getSubPath( struct tm *time )
|
||||
{
|
||||
static char subpath[PATH_MAX] = "";
|
||||
|
@ -148,7 +159,11 @@ public:
|
|||
return( Event::getSubPath( localtime( time ) ) );
|
||||
}
|
||||
|
||||
public:
|
||||
char* getEventFile(void){
|
||||
return video_file;
|
||||
}
|
||||
|
||||
public:
|
||||
static int PreAlarmCount()
|
||||
{
|
||||
return( pre_alarm_count );
|
||||
|
@ -189,11 +204,10 @@ public:
|
|||
|
||||
class EventStream : public StreamBase
|
||||
{
|
||||
public:
|
||||
public:
|
||||
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
||||
|
||||
protected:
|
||||
<<<<<<< HEAD
|
||||
protected:
|
||||
struct FrameData {
|
||||
//unsigned long id;
|
||||
time_t timestamp;
|
||||
|
@ -213,45 +227,25 @@ protected:
|
|||
char path[PATH_MAX];
|
||||
int n_frames;
|
||||
FrameData *frames;
|
||||
};
|
||||
=======
|
||||
struct FrameData {
|
||||
//unsigned long id;
|
||||
time_t timestamp;
|
||||
time_t offset;
|
||||
double delta;
|
||||
bool in_db;
|
||||
char video_file[PATH_MAX];
|
||||
};
|
||||
|
||||
struct EventData
|
||||
{
|
||||
unsigned long event_id;
|
||||
unsigned long monitor_id;
|
||||
unsigned long frame_count;
|
||||
time_t start_time;
|
||||
double duration;
|
||||
char path[PATH_MAX];
|
||||
int n_frames;
|
||||
FrameData *frames;
|
||||
};
|
||||
>>>>>>> master
|
||||
|
||||
protected:
|
||||
protected:
|
||||
static const int STREAM_PAUSE_WAIT = 250000; // Microseconds
|
||||
|
||||
static const StreamMode DEFAULT_MODE = MODE_SINGLE;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
StreamMode mode;
|
||||
bool forceEventChange;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
int curr_frame_id;
|
||||
double curr_stream_time;
|
||||
|
||||
EventData *event_data;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
bool loadEventData( int event_id );
|
||||
bool loadInitialEventData( int init_event_id, unsigned int init_frame_id );
|
||||
bool loadInitialEventData( int monitor_id, time_t event_time );
|
||||
|
@ -260,7 +254,7 @@ protected:
|
|||
void processCommand( const CmdMsg *msg );
|
||||
bool sendFrame( int delta_us );
|
||||
|
||||
public:
|
||||
public:
|
||||
EventStream()
|
||||
{
|
||||
mode = DEFAULT_MODE;
|
||||
|
@ -287,6 +281,7 @@ public:
|
|||
mode = p_mode;
|
||||
}
|
||||
void runStream();
|
||||
Image *getImage();
|
||||
};
|
||||
|
||||
#endif // ZM_EVENT_H
|
||||
|
|
|
@ -23,6 +23,16 @@
|
|||
|
||||
#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE
|
||||
|
||||
void FFMPEGInit() {
|
||||
static bool bInit = false;
|
||||
|
||||
if(!bInit) {
|
||||
av_register_all();
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
bInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_LIBAVUTIL
|
||||
enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) {
|
||||
enum _AVPIXELFORMAT pf;
|
||||
|
@ -140,10 +150,7 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint
|
|||
Error("NULL Input or output buffer");
|
||||
return -1;
|
||||
}
|
||||
if(in_pf == 0 || out_pf == 0) {
|
||||
Error("Invalid input or output pixel formats");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(!width || !height) {
|
||||
Error("Invalid width or height");
|
||||
return -3;
|
||||
|
@ -160,30 +167,55 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint
|
|||
#endif
|
||||
|
||||
/* Check the buffer sizes */
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
size_t insize = av_image_get_buffer_size(in_pf, width, height,1);
|
||||
#else
|
||||
size_t insize = avpicture_get_size(in_pf, width, height);
|
||||
#endif
|
||||
if(insize != in_buffer_size) {
|
||||
Error("The input buffer size does not match the expected size for the input format. Required: %d Available: %d", insize, in_buffer_size);
|
||||
return -4;
|
||||
}
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
size_t outsize = av_image_get_buffer_size(out_pf, width, height,1);
|
||||
#else
|
||||
size_t outsize = avpicture_get_size(out_pf, width, height);
|
||||
#endif
|
||||
|
||||
if(outsize < out_buffer_size) {
|
||||
Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size);
|
||||
return -5;
|
||||
}
|
||||
|
||||
/* Get the context */
|
||||
swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL );
|
||||
swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL );
|
||||
if(swscale_ctx == NULL) {
|
||||
Error("Failed getting swscale context");
|
||||
return -6;
|
||||
}
|
||||
|
||||
/* Fill in the buffers */
|
||||
if(!avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer, in_pf, width, height ) ) {
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
if(av_image_fill_arrays(input_avframe->data, input_avframe->linesize,
|
||||
(uint8_t*)in_buffer, in_pf, width, height, 1) <= 0)
|
||||
{
|
||||
#else
|
||||
if(avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer,
|
||||
in_pf, width, height ) <= 0)
|
||||
{
|
||||
#endif
|
||||
Error("Failed filling input frame with input buffer");
|
||||
return -7;
|
||||
}
|
||||
if(!avpicture_fill( (AVPicture*)output_avframe, out_buffer, out_pf, width, height ) ) {
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
if(av_image_fill_arrays(output_avframe->data, output_avframe->linesize,
|
||||
out_buffer, out_pf, width, height, 1) <= 0)
|
||||
{
|
||||
#else
|
||||
if(avpicture_fill( (AVPicture*)output_avframe, out_buffer,
|
||||
out_pf, width, height ) <= 0)
|
||||
{
|
||||
#endif
|
||||
Error("Failed filling output frame with output buffer");
|
||||
return -8;
|
||||
}
|
||||
|
@ -233,3 +265,173 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si
|
|||
#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||
|
||||
#endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE
|
||||
|
||||
#if HAVE_LIBAVUTIL
|
||||
int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){
|
||||
int64_t a, b, this_thing;
|
||||
|
||||
av_assert0(in_ts != AV_NOPTS_VALUE);
|
||||
av_assert0(duration >= 0);
|
||||
|
||||
if (*last == AV_NOPTS_VALUE || !duration || in_tb.num*(int64_t)out_tb.den <= out_tb.num*(int64_t)in_tb.den) {
|
||||
simple_round:
|
||||
*last = av_rescale_q(in_ts, in_tb, fs_tb) + duration;
|
||||
return av_rescale_q(in_ts, in_tb, out_tb);
|
||||
}
|
||||
|
||||
a = av_rescale_q_rnd(2*in_ts-1, in_tb, fs_tb, AV_ROUND_DOWN) >>1;
|
||||
b = (av_rescale_q_rnd(2*in_ts+1, in_tb, fs_tb, AV_ROUND_UP )+1)>>1;
|
||||
if (*last < 2*a - b || *last > 2*b - a)
|
||||
goto simple_round;
|
||||
|
||||
this_thing = av_clip64(*last, a, b);
|
||||
*last = this_thing + duration;
|
||||
|
||||
return av_rescale_q(this_thing, fs_tb, out_tb);
|
||||
}
|
||||
#endif
|
||||
|
||||
int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) {
|
||||
AVFormatContext *s = avformat_alloc_context();
|
||||
int ret = 0;
|
||||
|
||||
*avctx = NULL;
|
||||
if (!s) {
|
||||
av_log(s, AV_LOG_ERROR, "Out of memory\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!oformat) {
|
||||
if (format) {
|
||||
oformat = av_guess_format(format, NULL, NULL);
|
||||
if (!oformat) {
|
||||
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
|
||||
ret = AVERROR(EINVAL);
|
||||
}
|
||||
} else {
|
||||
oformat = av_guess_format(NULL, filename, NULL);
|
||||
if (!oformat) {
|
||||
ret = AVERROR(EINVAL);
|
||||
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
avformat_free_context(s);
|
||||
return ret;
|
||||
} else {
|
||||
s->oformat = oformat;
|
||||
if (s->oformat->priv_data_size > 0) {
|
||||
s->priv_data = av_mallocz(s->oformat->priv_data_size);
|
||||
if (s->priv_data) {
|
||||
if (s->oformat->priv_class) {
|
||||
*(const AVClass**)s->priv_data= s->oformat->priv_class;
|
||||
av_opt_set_defaults(s->priv_data);
|
||||
}
|
||||
} else {
|
||||
av_log(s, AV_LOG_ERROR, "Out of memory\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
return ret;
|
||||
}
|
||||
s->priv_data = NULL;
|
||||
}
|
||||
|
||||
if (filename) strncpy(s->filename, filename, sizeof(s->filename));
|
||||
*avctx = s;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void zm_log_fps(double d, const char *postfix)
|
||||
{
|
||||
uint64_t v = lrintf(d * 100);
|
||||
if (!v) {
|
||||
Debug(3, "%1.4f %s", d, postfix);
|
||||
} else if (v % 100) {
|
||||
Debug(3, "%3.2f %s", d, postfix);
|
||||
} else if (v % (100 * 1000)) {
|
||||
Debug(3, "%1.0f %s", d, postfix);
|
||||
} else
|
||||
Debug(3, "%1.0fk %s", d / 1000, postfix);
|
||||
}
|
||||
|
||||
/* "user interface" functions */
|
||||
void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) {
|
||||
char buf[256];
|
||||
int flags = (is_output ? ic->oformat->flags : ic->iformat->flags);
|
||||
AVStream *st = ic->streams[i];
|
||||
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
||||
char separator = '\n';
|
||||
|
||||
avcodec_string(buf, sizeof(buf), st->codec, is_output);
|
||||
Debug(3, " Stream #%d:%d", index, i);
|
||||
|
||||
/* the pid is an important information, so we display it */
|
||||
/* XXX: add a generic system */
|
||||
if (flags & AVFMT_SHOW_IDS)
|
||||
Debug(3, "[0x%x]", st->id);
|
||||
if (lang)
|
||||
Debug(3, "(%s)", lang->value);
|
||||
av_log(NULL, AV_LOG_DEBUG, ", %d, %d/%d", st->codec_info_nb_frames,
|
||||
st->time_base.num, st->time_base.den);
|
||||
Debug(3, ": %s", buf);
|
||||
|
||||
if (st->sample_aspect_ratio.num && // default
|
||||
av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) {
|
||||
AVRational display_aspect_ratio;
|
||||
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
|
||||
st->codec->width * (int64_t)st->sample_aspect_ratio.num,
|
||||
st->codec->height * (int64_t)st->sample_aspect_ratio.den,
|
||||
1024 * 1024);
|
||||
Debug(3, ", SAR %d:%d DAR %d:%d",
|
||||
st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
|
||||
display_aspect_ratio.num, display_aspect_ratio.den);
|
||||
}
|
||||
|
||||
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
int fps = st->avg_frame_rate.den && st->avg_frame_rate.num;
|
||||
int tbr = st->r_frame_rate.den && st->r_frame_rate.num;
|
||||
int tbn = st->time_base.den && st->time_base.num;
|
||||
int tbc = st->codec->time_base.den && st->codec->time_base.num;
|
||||
|
||||
if (fps || tbr || tbn || tbc)
|
||||
Debug(3, "%s", separator);
|
||||
|
||||
if (fps)
|
||||
zm_log_fps(av_q2d(st->avg_frame_rate), tbr || tbn || tbc ? "fps, " : "fps");
|
||||
if (tbr)
|
||||
zm_log_fps(av_q2d(st->r_frame_rate), tbn || tbc ? "tbr, " : "tbr");
|
||||
if (tbn)
|
||||
zm_log_fps(1 / av_q2d(st->time_base), tbc ? "tbn, " : "tbn");
|
||||
if (tbc)
|
||||
zm_log_fps(1 / av_q2d(st->codec->time_base), "tbc");
|
||||
}
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
Debug(3, " (default)");
|
||||
if (st->disposition & AV_DISPOSITION_DUB)
|
||||
Debug(3, " (dub)");
|
||||
if (st->disposition & AV_DISPOSITION_ORIGINAL)
|
||||
Debug(3, " (original)");
|
||||
if (st->disposition & AV_DISPOSITION_COMMENT)
|
||||
Debug(3, " (comment)");
|
||||
if (st->disposition & AV_DISPOSITION_LYRICS)
|
||||
Debug(3, " (lyrics)");
|
||||
if (st->disposition & AV_DISPOSITION_KARAOKE)
|
||||
Debug(3, " (karaoke)");
|
||||
if (st->disposition & AV_DISPOSITION_FORCED)
|
||||
Debug(3, " (forced)");
|
||||
if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||
Debug(3, " (hearing impaired)");
|
||||
if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
||||
Debug(3, " (visual impaired)");
|
||||
if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS)
|
||||
Debug(3, " (clean effects)");
|
||||
Debug(3, "\n");
|
||||
|
||||
//dump_metadata(NULL, st->metadata, " ");
|
||||
|
||||
//dump_sidedata(NULL, st, " ");
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ extern "C" {
|
|||
|
||||
// AVUTIL
|
||||
#if HAVE_LIBAVUTIL_AVUTIL_H
|
||||
#include "libavutil/avassert.h"
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/base64.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
@ -47,6 +48,12 @@ extern "C" {
|
|||
#else
|
||||
#include <libavcodec/opt.h>
|
||||
#endif
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
#include <libavutil/imgutils.h>
|
||||
#else
|
||||
#include <libavutil/avutil.h>
|
||||
#endif
|
||||
#elif HAVE_FFMPEG_AVUTIL_H
|
||||
#include <ffmpeg/avutil.h>
|
||||
#include <ffmpeg/base64.h>
|
||||
|
@ -194,6 +201,9 @@ extern "C" {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
/* A single function to initialize ffmpeg, to avoid multiple initializations */
|
||||
void FFMPEGInit();
|
||||
|
||||
#if HAVE_LIBAVUTIL
|
||||
enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder);
|
||||
#endif // HAVE_LIBAVUTIL
|
||||
|
@ -275,4 +285,36 @@ protected:
|
|||
|
||||
#endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H )
|
||||
|
||||
#ifndef avformat_alloc_output_context2
|
||||
int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename);
|
||||
#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a)
|
||||
#endif
|
||||
|
||||
#ifndef av_rescale_delta
|
||||
/**
|
||||
* Rescale a timestamp while preserving known durations.
|
||||
*/
|
||||
int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
|
||||
#endif
|
||||
|
||||
#ifndef av_clip64
|
||||
/**
|
||||
* Clip a signed 64bit integer value into the amin-amax range.
|
||||
* @param a value to clip
|
||||
* @param amin minimum value of the clip range
|
||||
* @param amax maximum value of the clip range
|
||||
* @return clipped value
|
||||
*/
|
||||
static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax)
|
||||
{
|
||||
if (a < amin) return amin;
|
||||
else if (a > amax) return amax;
|
||||
else return a;
|
||||
}
|
||||
|
||||
#define av_clip64 av_clip64_c
|
||||
#endif
|
||||
|
||||
void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output);
|
||||
|
||||
#endif // ZM_FFMPEG_H
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
|
||||
#include "zm_ffmpeg_camera.h"
|
||||
|
||||
extern "C"{
|
||||
#include "libavutil/time.h"
|
||||
}
|
||||
#ifndef AV_ERROR_MAX_STRING_SIZE
|
||||
#define AV_ERROR_MAX_STRING_SIZE 64
|
||||
#endif
|
||||
|
@ -33,8 +36,8 @@
|
|||
#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 )
|
||||
|
@ -46,15 +49,19 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
|
|||
|
||||
mFormatContext = NULL;
|
||||
mVideoStreamId = -1;
|
||||
mAudioStreamId = -1;
|
||||
mCodecContext = NULL;
|
||||
mCodec = NULL;
|
||||
mRawFrame = NULL;
|
||||
mFrame = NULL;
|
||||
frameCount = 0;
|
||||
startTime=0;
|
||||
mIsOpening = false;
|
||||
mCanCapture = false;
|
||||
mOpenStart = 0;
|
||||
mReopenThread = 0;
|
||||
wasRecording = false;
|
||||
videoStore = NULL;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
mConvertContext = NULL;
|
||||
|
@ -93,6 +100,7 @@ void FfmpegCamera::Initialise()
|
|||
av_log_set_level( AV_LOG_QUIET );
|
||||
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
}
|
||||
|
||||
void FfmpegCamera::Terminate()
|
||||
|
@ -101,6 +109,8 @@ void FfmpegCamera::Terminate()
|
|||
|
||||
int FfmpegCamera::PrimeCapture()
|
||||
{
|
||||
mVideoStreamId = -1;
|
||||
mAudioStreamId = -1;
|
||||
Info( "Priming capture from %s", mPath.c_str() );
|
||||
|
||||
if (OpenFfmpeg() != 0){
|
||||
|
@ -160,7 +170,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();
|
||||
}
|
||||
|
||||
|
@ -168,6 +178,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)
|
||||
|
@ -179,11 +190,16 @@ int FfmpegCamera::Capture( Image &image )
|
|||
|
||||
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
||||
|
||||
if ( frameComplete )
|
||||
{
|
||||
Debug( 3, "Got frame %d", frameCount );
|
||||
if ( frameComplete ) {
|
||||
Debug( 4, "Got frame %d", frameCount );
|
||||
|
||||
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
||||
directbuffer, imagePixFormat, width, height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
||||
imagePixFormat, width, height);
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
if(mConvertContext == NULL) {
|
||||
|
@ -200,16 +216,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()
|
||||
{
|
||||
|
@ -284,6 +302,12 @@ int FfmpegCamera::OpenFfmpeg() {
|
|||
mIsOpening = false;
|
||||
Debug ( 1, "Opened input" );
|
||||
|
||||
Info( "Stream open %s", mPath.c_str() );
|
||||
startTime=av_gettime();//FIXME here or after find_Stream_info
|
||||
|
||||
//FIXME can speed up initial analysis but need sensible parameters...
|
||||
//mFormatContext->probesize = 32;
|
||||
//mFormatContext->max_analyze_duration = 32;
|
||||
// Locate stream info from avformat_open_input
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
|
||||
Debug ( 1, "Calling av_find_stream_info" );
|
||||
|
@ -297,7 +321,9 @@ int FfmpegCamera::OpenFfmpeg() {
|
|||
Debug ( 1, "Got stream info" );
|
||||
|
||||
// Find first video stream present
|
||||
// The one we want Might not be the first
|
||||
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))
|
||||
|
@ -306,14 +332,34 @@ 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 (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( 2, "Unable to locate audio stream in %s", mPath.c_str() );
|
||||
|
||||
Debug ( 1, "Found video stream" );
|
||||
Debug ( 3, "Found video stream at index %d", mVideoStreamId );
|
||||
Debug ( 3, "Found audio stream at index %d", mAudioStreamId );
|
||||
|
||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||
|
||||
|
@ -322,6 +368,7 @@ int FfmpegCamera::OpenFfmpeg() {
|
|||
Fatal( "Can't find codec for video stream from %s", mPath.c_str() );
|
||||
|
||||
Debug ( 1, "Found decoder" );
|
||||
zm_dump_stream_format( mFormatContext, mVideoStreamId, 0, 0 );
|
||||
|
||||
// Open the codec
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||
|
@ -354,12 +401,17 @@ int FfmpegCamera::OpenFfmpeg() {
|
|||
|
||||
Debug ( 1, "Allocated frames" );
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
int pSize = av_image_get_buffer_size( imagePixFormat, width, height,1 );
|
||||
#else
|
||||
int pSize = avpicture_get_size( imagePixFormat, width, height );
|
||||
#endif
|
||||
|
||||
if( (unsigned int)pSize != imagesize) {
|
||||
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
|
||||
}
|
||||
|
||||
Debug ( 1, "Validated imagesize" );
|
||||
Debug ( 1, "Validated imagesize %d", pSize );
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
Debug ( 1, "Calling sws_isSupportedInput" );
|
||||
|
@ -470,4 +522,174 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
|
|||
}
|
||||
}
|
||||
|
||||
//Function to handle capture and store
|
||||
int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_file )
|
||||
{
|
||||
if (!mCanCapture){
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
||||
if (mReopenThread != 0) {
|
||||
void *retval = 0;
|
||||
int ret;
|
||||
|
||||
ret = pthread_join(mReopenThread, &retval);
|
||||
if (ret != 0){
|
||||
Error("Could not join reopen thread.");
|
||||
}
|
||||
|
||||
Info( "Successfully reopened stream." );
|
||||
mReopenThread = 0;
|
||||
}
|
||||
|
||||
AVPacket packet;
|
||||
uint8_t* directbuffer;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
if ( mCodecContext->codec_id != AV_CODEC_ID_H264 ) {
|
||||
Error( "Input stream is not h264. The stored event file may not be viewable in browser." );
|
||||
}
|
||||
|
||||
int frameComplete = false;
|
||||
while ( !frameComplete ) {
|
||||
int avResult = av_read_frame( mFormatContext, &packet );
|
||||
if ( avResult < 0 ) {
|
||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
||||
if (
|
||||
// Check if EOF.
|
||||
(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();
|
||||
}
|
||||
|
||||
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf );
|
||||
return( -1 );
|
||||
}
|
||||
Debug( 5, "Got packet from stream %d", packet.stream_index );
|
||||
if ( packet.stream_index == mVideoStreamId ) {
|
||||
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
||||
if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 )
|
||||
#else
|
||||
if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 )
|
||||
#endif
|
||||
Fatal( "Unable to decode frame at frame %d", frameCount );
|
||||
|
||||
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
||||
|
||||
if ( frameComplete ) {
|
||||
Debug( 4, "Got frame %d", frameCount );
|
||||
|
||||
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
|
||||
|
||||
//Keep the last keyframe so we can establish immediate video
|
||||
if(packet.flags & AV_PKT_FLAG_KEY) {
|
||||
//Debug(4, "Have keyframe");
|
||||
//av_copy_packet(&lastKeyframePkt, &packet);
|
||||
//TODO I think we need to store the key frame location for seeking as part of the event
|
||||
}
|
||||
|
||||
//Video recording
|
||||
if ( recording && !wasRecording ) {
|
||||
//Instantiate the video storage module
|
||||
Debug(3, "recording and ! wasRecording %s", event_file);
|
||||
|
||||
videoStore = new VideoStore((const char *)event_file, "mp4", mFormatContext->streams[mVideoStreamId],mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],startTime);
|
||||
wasRecording = true;
|
||||
strcpy(oldDirectory, event_file);
|
||||
|
||||
|
||||
// Need to write out all the frames from the last keyframe?
|
||||
|
||||
} 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
|
||||
// for efficiency's sake, we should test for keyframe before we test for directory change...
|
||||
if ( recording && wasRecording && (packet.flags & AV_PKT_FLAG_KEY) && (strcmp(oldDirectory, event_file) != 0 ) ) {
|
||||
// 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 ) {
|
||||
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 for %s", mPath.c_str() );
|
||||
}
|
||||
|
||||
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 ffmpeg cameras" );
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
|
||||
frameCount++;
|
||||
} else {
|
||||
Debug( 3, "Not framecomplete after av_read_frame" );
|
||||
} // end if frameComplete
|
||||
} else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams
|
||||
Debug( 4, "Audio stream index %d", packet.stream_index );
|
||||
if ( frameComplete ) {
|
||||
Debug( 3, "Got audio frame with framecomplete %d", frameCount );
|
||||
//} else {
|
||||
//Debug( 3, "Got audio frame %d without frameComplete", frameCount );
|
||||
}
|
||||
if ( videoStore && recording ) {
|
||||
if ( record_audio ) {
|
||||
Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index );
|
||||
//Write the packet to our video store
|
||||
//FIXME no relevance of last key frame
|
||||
int ret = videoStore->writeAudioFramePacket( &packet, mFormatContext->streams[packet.stream_index] );
|
||||
if ( ret < 0 ) {//Less than zero and we skipped a frame
|
||||
av_free_packet( &packet );
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
Debug(4, "Not recording audio packet" );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 23, 0, 23, 0)
|
||||
Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codec->codec_type) );
|
||||
#else
|
||||
Debug( 3, "Some other stream index %d", packet.stream_index );
|
||||
#endif
|
||||
}
|
||||
av_free_packet( &packet );
|
||||
} // end while ! frameComplete
|
||||
return (frameCount);
|
||||
}
|
||||
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "zm_buffer.h"
|
||||
//#include "zm_utils.h"
|
||||
#include "zm_ffmpeg.h"
|
||||
#include "zm_videostore.h"
|
||||
|
||||
//
|
||||
// Class representing 'ffmpeg' cameras, i.e. those which are
|
||||
|
@ -42,6 +43,7 @@ protected:
|
|||
#if HAVE_LIBAVFORMAT
|
||||
AVFormatContext *mFormatContext;
|
||||
int mVideoStreamId;
|
||||
int mAudioStreamId;
|
||||
AVCodecContext *mCodecContext;
|
||||
AVCodec *mCodec;
|
||||
AVFrame *mRawFrame;
|
||||
|
@ -59,12 +61,21 @@ protected:
|
|||
pthread_t mReopenThread;
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
||||
bool wasRecording;
|
||||
VideoStore *videoStore;
|
||||
char oldDirectory[4096];
|
||||
|
||||
// Last Key frame
|
||||
AVPacket lastKeyframePkt;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
struct SwsContext *mConvertContext;
|
||||
#endif
|
||||
|
||||
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 ); }
|
||||
|
@ -77,6 +88,7 @@ public:
|
|||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
||||
int PostCapture();
|
||||
};
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 ); }
|
||||
|
@ -46,6 +46,7 @@ public:
|
|||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
||||
};
|
||||
|
||||
#endif // ZM_FILE_CAMERA_H
|
||||
|
|
|
@ -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 )
|
||||
|
@ -211,6 +211,20 @@ int LibvlcCamera::Capture( Image &image )
|
|||
return (0);
|
||||
}
|
||||
|
||||
// Should not return -1 as cancels capture. Always wait for image if available.
|
||||
int LibvlcCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory )
|
||||
{
|
||||
while(!mLibvlcData.newImage.getValueImmediate())
|
||||
mLibvlcData.newImage.getUpdatedValue(1);
|
||||
|
||||
mLibvlcData.mutex.lock();
|
||||
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
|
||||
mLibvlcData.newImage.setValueImmediate(false);
|
||||
mLibvlcData.mutex.unlock();
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int LibvlcCamera::PostCapture()
|
||||
{
|
||||
return(0);
|
||||
|
|
|
@ -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 ); }
|
||||
|
@ -70,6 +70,7 @@ public:
|
|||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
||||
int PostCapture();
|
||||
};
|
||||
|
||||
|
|
|
@ -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 ),
|
||||
|
@ -626,7 +644,11 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel,
|
|||
if ( !tmpPicture )
|
||||
Fatal( "Could not allocate temporary picture" );
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
int pSize = av_image_get_buffer_size( imagePixFormat, width, height,1 );
|
||||
#else
|
||||
int pSize = avpicture_get_size( imagePixFormat, width, height );
|
||||
#endif
|
||||
if( (unsigned int)pSize != imagesize) {
|
||||
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
|
||||
}
|
||||
|
@ -859,7 +881,18 @@ void LocalCamera::Initialise()
|
|||
#endif
|
||||
if ( !capturePictures[i] )
|
||||
Fatal( "Could not allocate picture" );
|
||||
avpicture_fill( (AVPicture *)capturePictures[i], (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height );
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(capturePictures[i]->data,
|
||||
capturePictures[i]->linesize,
|
||||
(uint8_t*)v4l2_data.buffers[i].start,capturePixFormat,
|
||||
v4l2_data.fmt.fmt.pix.width,
|
||||
v4l2_data.fmt.fmt.pix.height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)capturePictures[i],
|
||||
(uint8_t*)v4l2_data.buffers[i].start, capturePixFormat,
|
||||
v4l2_data.fmt.fmt.pix.width,
|
||||
v4l2_data.fmt.fmt.pix.height );
|
||||
#endif
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
}
|
||||
|
||||
|
@ -1017,7 +1050,16 @@ void LocalCamera::Initialise()
|
|||
#endif
|
||||
if ( !capturePictures[i] )
|
||||
Fatal( "Could not allocate picture" );
|
||||
avpicture_fill( (AVPicture *)capturePictures[i], (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], capturePixFormat, width, height );
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(capturePictures[i]->data,
|
||||
capturePictures[i]->linesize,
|
||||
(unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i],
|
||||
capturePixFormat, width, height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)capturePictures[i],
|
||||
(unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i],
|
||||
capturePixFormat, width, height );
|
||||
#endif
|
||||
}
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
|
||||
|
@ -2113,7 +2155,14 @@ int LocalCamera::Capture( Image &image )
|
|||
|
||||
Debug( 9, "Calling sws_scale to perform the conversion" );
|
||||
/* Use swscale to convert the image directly into the shared memory */
|
||||
avpicture_fill( (AVPicture *)tmpPicture, directbuffer, imagePixFormat, width, height );
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(tmpPicture->data,
|
||||
tmpPicture->linesize, directbuffer,
|
||||
imagePixFormat, width, height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)tmpPicture, directbuffer,
|
||||
imagePixFormat, width, height );
|
||||
#endif
|
||||
sws_scale( imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, tmpPicture->linesize );
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -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();
|
||||
|
@ -138,6 +156,7 @@ public:
|
|||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
||||
|
||||
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
|
||||
};
|
||||
|
|
|
@ -593,7 +593,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
|
|||
if ( level <= mFileLevel )
|
||||
{
|
||||
fprintf( mLogFileFP, "%s", logString );
|
||||
if ( mFlush )
|
||||
//if ( mFlush )
|
||||
fflush( mLogFileFP );
|
||||
}
|
||||
*syslogEnd = '\0';
|
||||
|
@ -615,7 +615,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
|
|||
{
|
||||
int priority = smSyslogPriorities[level];
|
||||
//priority |= LOG_DAEMON;
|
||||
syslog( priority, "%s [%s]", classString, syslogStart );
|
||||
syslog( priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart );
|
||||
}
|
||||
|
||||
if ( level <= FATAL )
|
||||
|
|
1362
src/zm_monitor.cpp
1362
src/zm_monitor.cpp
File diff suppressed because it is too large
Load Diff
339
src/zm_monitor.h
339
src/zm_monitor.h
|
@ -48,9 +48,9 @@
|
|||
//
|
||||
class Monitor
|
||||
{
|
||||
friend class MonitorStream;
|
||||
friend class MonitorStream;
|
||||
|
||||
public:
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
QUERY=0,
|
||||
|
@ -87,7 +87,7 @@ public:
|
|||
TAPE
|
||||
} State;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
typedef std::set<Zone *> ZoneSet;
|
||||
|
||||
typedef enum { GET_SETTINGS=0x1, SET_SETTINGS=0x2, RELOAD=0x4, SUSPEND=0x10, RESUME=0x20 } Action;
|
||||
|
@ -154,8 +154,20 @@ protected:
|
|||
void* padding;
|
||||
};
|
||||
|
||||
class MonitorLink
|
||||
//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
|
||||
{
|
||||
uint32_t size;
|
||||
char event_file[4096];
|
||||
uint32_t recording; //bool arch dependent so use uint32 instead
|
||||
//uint32_t frameNumber;
|
||||
} VideoStoreData;
|
||||
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
||||
class MonitorLink {
|
||||
protected:
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
|
@ -169,12 +181,12 @@ protected:
|
|||
#else // ZM_MEM_MAPPED
|
||||
int shm_id;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
<<<<<<< HEAD
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
|
||||
volatile SharedData *shared_data;
|
||||
volatile TriggerData *trigger_data;
|
||||
volatile VideoStoreData *video_store_data;
|
||||
|
||||
int last_state;
|
||||
int last_event;
|
||||
|
@ -184,21 +196,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 );
|
||||
}
|
||||
|
||||
|
@ -210,7 +218,7 @@ protected:
|
|||
bool hasAlarmed();
|
||||
};
|
||||
|
||||
protected:
|
||||
protected:
|
||||
// These are read from the DB and thereafter remain unchanged
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
|
@ -224,6 +232,13 @@ protected:
|
|||
unsigned int v4l_captures_per_frame;
|
||||
Orientation orientation; // Whether the image has to be rotated at all
|
||||
unsigned int deinterlacing;
|
||||
|
||||
int savejpegspref;
|
||||
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
|
||||
int hue; // The statically saved hue of the camera
|
||||
|
@ -276,112 +291,6 @@ protected:
|
|||
unsigned int last_motion_score;
|
||||
|
||||
EventCloseMode event_close_mode;
|
||||
=======
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
|
||||
volatile SharedData *shared_data;
|
||||
volatile TriggerData *trigger_data;
|
||||
|
||||
int last_state;
|
||||
int last_event;
|
||||
|
||||
public:
|
||||
MonitorLink( int p_id, const char *p_name );
|
||||
~MonitorLink();
|
||||
|
||||
inline int Id() const
|
||||
{
|
||||
return( id );
|
||||
}
|
||||
inline const char *Name() const
|
||||
{
|
||||
return( name );
|
||||
}
|
||||
|
||||
inline bool isConnected() const
|
||||
{
|
||||
return( connected );
|
||||
}
|
||||
inline time_t getLastConnectTime() const
|
||||
{
|
||||
return( last_connect_time );
|
||||
}
|
||||
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
|
||||
bool isAlarmed();
|
||||
bool inAlarm();
|
||||
bool hasAlarmed();
|
||||
};
|
||||
|
||||
protected:
|
||||
// These are read from the DB and thereafter remain unchanged
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
unsigned int server_id;
|
||||
Function function; // What the monitor is doing
|
||||
bool enabled; // Whether the monitor is enabled or asleep
|
||||
unsigned int width; // Normally the same as the camera, but not if partly rotated
|
||||
unsigned int height; // Normally the same as the camera, but not if partly rotated
|
||||
bool v4l_multi_buffer;
|
||||
unsigned int v4l_captures_per_frame;
|
||||
Orientation orientation; // Whether the image has to be rotated at all
|
||||
unsigned int deinterlacing;
|
||||
int brightness; // The statically saved brightness of the camera
|
||||
int contrast; // The statically saved contrast of the camera
|
||||
int hue; // The statically saved hue of the camera
|
||||
int colour; // The statically saved colour of the camera
|
||||
char event_prefix[64]; // The prefix applied to event names as they are created
|
||||
char label_format[64]; // The format of the timestamp on the images
|
||||
Coord label_coord; // The coordinates of the timestamp on the images
|
||||
int label_size; // Size of the timestamp on the images
|
||||
int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count
|
||||
int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate,
|
||||
// value is pre_event_count + alarm_frame_count - 1
|
||||
int warmup_count; // How many images to process before looking for events
|
||||
int pre_event_count; // How many images to hold and prepend to an alarm event
|
||||
int post_event_count; // How many unalarmed images must occur before the alarm state is reset
|
||||
int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
|
||||
int section_length; // How long events should last in continuous modes
|
||||
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor
|
||||
int frame_skip; // How many frames to skip in continuous modes
|
||||
int motion_frame_skip; // How many frames to skip in motion detection
|
||||
double analysis_fps; // Target framerate for video analysis
|
||||
unsigned int analysis_update_delay; // How long we wait before updating analysis parameters
|
||||
int capture_delay; // How long we wait between capture frames
|
||||
int alarm_capture_delay; // How long we wait between capture frames when in alarm state
|
||||
int alarm_frame_count; // How many alarm frames are required before an event is triggered
|
||||
int fps_report_interval; // How many images should be captured/processed between reporting the current FPS
|
||||
int ref_blend_perc; // Percentage of new image going into reference image.
|
||||
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
|
||||
bool track_motion; // Whether this monitor tries to track detected motion
|
||||
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
|
||||
bool embed_exif; // Whether to embed Exif data into each image frame or not
|
||||
|
||||
double fps;
|
||||
Image delta_image;
|
||||
Image ref_image;
|
||||
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
|
||||
Image write_image; // Used when creating snapshot images
|
||||
|
||||
Purpose purpose; // What this monitor has been created to do
|
||||
int event_count;
|
||||
int image_count;
|
||||
int ready_count;
|
||||
int first_alarm_count;
|
||||
int last_alarm_count;
|
||||
int buffer_count;
|
||||
int prealarm_count;
|
||||
State state;
|
||||
time_t start_time;
|
||||
time_t last_fps_time;
|
||||
time_t auto_resume_time;
|
||||
unsigned int last_motion_score;
|
||||
|
||||
EventCloseMode event_close_mode;
|
||||
>>>>>>> master
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
int map_fd;
|
||||
|
@ -389,20 +298,13 @@ protected:
|
|||
#else // ZM_MEM_MAPPED
|
||||
int shm_id;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
<<<<<<< HEAD
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
Storage *storage;
|
||||
|
||||
SharedData *shared_data;
|
||||
TriggerData *trigger_data;
|
||||
=======
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
>>>>>>> master
|
||||
|
||||
SharedData *shared_data;
|
||||
TriggerData *trigger_data;
|
||||
VideoStoreData *video_store_data;
|
||||
|
||||
Snapshot *image_buffer;
|
||||
Snapshot next_buffer; /* Used by four field deinterlacing */
|
||||
|
@ -423,131 +325,106 @@ protected:
|
|||
int n_linked_monitors;
|
||||
MonitorLink **linked_monitors;
|
||||
|
||||
public:
|
||||
// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info.
|
||||
//bool OurCheckAlarms( Zone *zone, const Image *pImage );
|
||||
<<<<<<< HEAD
|
||||
Monitor( int p_id, const char *p_name, unsigned int p_server_id, unsigned int p_storage_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, 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 );
|
||||
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,
|
||||
unsigned int p_storage_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 Storage *getStorage()
|
||||
{
|
||||
inline Storage *getStorage() {
|
||||
if ( ! storage ) {
|
||||
storage = new Storage( storage_id );
|
||||
}
|
||||
return( storage );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
|
||||
unsigned int Width() const { return( width ); }
|
||||
unsigned int Height() const { return( height ); }
|
||||
unsigned int Width() const { return width; }
|
||||
unsigned int Height() const { return height; }
|
||||
unsigned int Colours() const { return( camera->Colours() ); }
|
||||
unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); }
|
||||
|
||||
=======
|
||||
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, 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
|
||||
{
|
||||
return( shared_data->valid );
|
||||
}
|
||||
|
||||
inline int Id() const
|
||||
{
|
||||
return( id );
|
||||
}
|
||||
inline const char *Name() const
|
||||
{
|
||||
return( name );
|
||||
}
|
||||
inline Function GetFunction() const
|
||||
{
|
||||
return( function );
|
||||
}
|
||||
inline bool Enabled()
|
||||
{
|
||||
if ( function <= MONITOR )
|
||||
return( false );
|
||||
return( enabled );
|
||||
}
|
||||
inline const char *EventPrefix() const
|
||||
{
|
||||
return( event_prefix );
|
||||
}
|
||||
inline bool Ready()
|
||||
{
|
||||
if ( function <= MONITOR )
|
||||
return( false );
|
||||
return( image_count > ready_count );
|
||||
}
|
||||
inline bool Active()
|
||||
{
|
||||
if ( function <= MONITOR )
|
||||
return( false );
|
||||
return( enabled && shared_data->active );
|
||||
}
|
||||
inline bool Exif()
|
||||
{
|
||||
return( embed_exif );
|
||||
}
|
||||
|
||||
unsigned int Width() const { return( width ); }
|
||||
unsigned int Height() const { return( height ); }
|
||||
unsigned int Colours() const { return( camera->Colours() ); }
|
||||
unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); }
|
||||
|
||||
>>>>>>> master
|
||||
int GetOptSaveJPEGs() const { return( savejpegspref ); }
|
||||
int GetOptVideoWriter() const { return( videowriterpref ); }
|
||||
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
|
||||
|
||||
State GetState() const;
|
||||
int GetImage( int index=-1, int scale=100 );
|
||||
|
@ -577,17 +454,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() );
|
||||
}
|
||||
|
||||
|
@ -630,51 +504,46 @@ public:
|
|||
|
||||
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
|
||||
|
||||
class MonitorStream : public StreamBase
|
||||
{
|
||||
protected:
|
||||
class MonitorStream : public StreamBase {
|
||||
protected:
|
||||
typedef struct SwapImage {
|
||||
bool valid;
|
||||
struct timeval timestamp;
|
||||
char file_name[PATH_MAX];
|
||||
} SwapImage;
|
||||
|
||||
private:
|
||||
private:
|
||||
SwapImage *temp_image_buffer;
|
||||
int temp_image_buffer_count;
|
||||
int temp_read_index;
|
||||
int temp_write_index;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
time_t ttl;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
int playback_buffer;
|
||||
bool delayed;
|
||||
|
||||
int frame_count;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
bool checkSwapPath( const char *path, bool create_path );
|
||||
|
||||
bool sendFrame( const char *filepath, struct timeval *timestamp );
|
||||
bool sendFrame( Image *image, struct timeval *timestamp );
|
||||
void processCommand( const CmdMsg *msg );
|
||||
|
||||
public:
|
||||
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 )
|
||||
{
|
||||
public:
|
||||
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();
|
||||
|
|
|
@ -333,7 +333,13 @@ void VideoStream::OpenStream( )
|
|||
Panic( "Could not allocate opicture" );
|
||||
}
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
int size = av_image_get_buffer_size( c->pix_fmt, c->width,
|
||||
c->height, 1 );
|
||||
#else
|
||||
int size = avpicture_get_size( c->pix_fmt, c->width, c->height );
|
||||
#endif
|
||||
|
||||
uint8_t *opicture_buf = (uint8_t *)av_malloc( size );
|
||||
if ( !opicture_buf )
|
||||
{
|
||||
|
@ -344,7 +350,13 @@ void VideoStream::OpenStream( )
|
|||
#endif
|
||||
Panic( "Could not allocate opicture_buf" );
|
||||
}
|
||||
avpicture_fill( (AVPicture *)opicture, opicture_buf, c->pix_fmt, c->width, c->height );
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(opicture->data, opicture->linesize,
|
||||
opicture_buf, c->pix_fmt, c->width, c->height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)opicture, opicture_buf, c->pix_fmt,
|
||||
c->width, c->height );
|
||||
#endif
|
||||
|
||||
/* if the output format is not identical to the input format, then a temporary
|
||||
picture is needed too. It is then converted to the required
|
||||
|
@ -361,7 +373,12 @@ void VideoStream::OpenStream( )
|
|||
{
|
||||
Panic( "Could not allocate tmp_opicture" );
|
||||
}
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
int size = av_image_get_buffer_size( pf, c->width,
|
||||
c->height,1 );
|
||||
#else
|
||||
int size = avpicture_get_size( pf, c->width, c->height );
|
||||
#endif
|
||||
uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size );
|
||||
if ( !tmp_opicture_buf )
|
||||
{
|
||||
|
@ -372,7 +389,14 @@ void VideoStream::OpenStream( )
|
|||
#endif
|
||||
Panic( "Could not allocate tmp_opicture_buf" );
|
||||
}
|
||||
avpicture_fill( (AVPicture *)tmp_opicture, tmp_opicture_buf, pf, c->width, c->height );
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(tmp_opicture->data,
|
||||
tmp_opicture->linesize, tmp_opicture_buf, pf,
|
||||
c->width, c->height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)tmp_opicture,
|
||||
tmp_opicture_buf, pf, c->width, c->height );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,14 +702,14 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size,
|
|||
#endif
|
||||
if ( got_packet )
|
||||
{
|
||||
if ( c->coded_frame->key_frame )
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2)
|
||||
pkt->flags |= AV_PKT_FLAG_KEY;
|
||||
#else
|
||||
pkt->flags |= PKT_FLAG_KEY;
|
||||
#endif
|
||||
}
|
||||
// if ( c->coded_frame->key_frame )
|
||||
// {
|
||||
//#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2)
|
||||
// pkt->flags |= AV_PKT_FLAG_KEY;
|
||||
//#else
|
||||
// pkt->flags |= PKT_FLAG_KEY;
|
||||
//#endif
|
||||
// }
|
||||
|
||||
if ( pkt->pts != (int64_t)AV_NOPTS_VALUE )
|
||||
{
|
||||
|
|
|
@ -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 ),
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
//
|
||||
// Class representing 'remote' cameras, i.e. those which are
|
||||
|
@ -55,7 +56,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 ); }
|
||||
|
@ -73,6 +89,7 @@ public:
|
|||
virtual int PreCapture() = 0;
|
||||
virtual int Capture( Image &image ) = 0;
|
||||
virtual int PostCapture() = 0;
|
||||
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory )=0;
|
||||
};
|
||||
|
||||
#endif // ZM_REMOTE_CAMERA_H
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -108,7 +108,12 @@ int RemoteCameraHttp::Connect()
|
|||
{
|
||||
close(sd);
|
||||
sd = -1;
|
||||
Warning("Can't connect to remote camera: %s", strerror(errno) );
|
||||
char buf[sizeof(struct in6_addr)];
|
||||
struct sockaddr_in *addr;
|
||||
addr = (struct sockaddr_in *)p->ai_addr;
|
||||
inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN );
|
||||
|
||||
Warning("Can't connect to remote camera mid: %d at %s: %s", id, buf, strerror(errno) );
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
@ -58,6 +58,7 @@ public:
|
|||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
||||
};
|
||||
|
||||
#endif // ZM_REMOTE_CAMERA_HTTP_H
|
||||
|
|
|
@ -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. I think in ffmpeg there are now some functions to do that.
|
||||
buffer.size( max_size );
|
||||
|
||||
if ( logDebugging() )
|
||||
|
@ -167,19 +172,41 @@ int RemoteCameraRtsp::PrimeCapture()
|
|||
|
||||
// Find first video stream present
|
||||
mVideoStreamId = -1;
|
||||
mAudioStreamId = -1;
|
||||
|
||||
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ )
|
||||
// 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 )
|
||||
#else
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
||||
#endif
|
||||
{
|
||||
if ( mVideoStreamId == -1 ) {
|
||||
mVideoStreamId = i;
|
||||
break;
|
||||
continue;
|
||||
} else {
|
||||
Debug(2, "Have another video stream." );
|
||||
}
|
||||
}
|
||||
#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" );
|
||||
if ( mAudioStreamId == -1 )
|
||||
Debug( 3, "Unable to locate audio stream" );
|
||||
|
||||
// Get a pointer to the codec context for the video stream
|
||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||
|
@ -214,7 +241,12 @@ int RemoteCameraRtsp::PrimeCapture()
|
|||
if(mRawFrame == NULL || mFrame == NULL)
|
||||
Fatal( "Unable to allocate frame(s)");
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
int pSize = av_image_get_buffer_size( imagePixFormat, width, height, 1 );
|
||||
#else
|
||||
int pSize = avpicture_get_size( imagePixFormat, width, height );
|
||||
#endif
|
||||
|
||||
if( (unsigned int)pSize != imagesize) {
|
||||
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
|
||||
}
|
||||
|
@ -236,8 +268,7 @@ int RemoteCameraRtsp::PrimeCapture()
|
|||
return( 0 );
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::PreCapture()
|
||||
{
|
||||
int RemoteCameraRtsp::PreCapture() {
|
||||
if ( !rtspThread->isRunning() )
|
||||
return( -1 );
|
||||
if ( !rtspThread->hasSources() )
|
||||
|
@ -248,8 +279,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 +291,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 +304,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;
|
||||
|
@ -299,21 +326,23 @@ int RemoteCameraRtsp::Capture( Image &image )
|
|||
buffer += lastSps;
|
||||
buffer += lastPps;
|
||||
}
|
||||
} else {
|
||||
Debug(3, "Not an h264 packet");
|
||||
}
|
||||
|
||||
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,15 +353,187 @@ 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) ;
|
||||
}
|
||||
|
||||
//int RemoteCameraRtsp::ReadData(void *opaque, uint8_t *buf, int bufSize) {
|
||||
|
||||
//if ( buffer.size() > bufSize ) {
|
||||
//buf = buffer.head();
|
||||
//buffer -= bufSize;
|
||||
//} else {
|
||||
//Error("Implement me");
|
||||
//return -1;
|
||||
//}
|
||||
//}
|
||||
|
||||
//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? Because it might be audio or something else.
|
||||
// Um... we just initialized packet... we can't be testing for what it is yet....
|
||||
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 );
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
||||
directbuffer, imagePixFormat, width, height, 1);
|
||||
#else
|
||||
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
||||
imagePixFormat, width, height);
|
||||
#endif
|
||||
|
||||
//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
|
||||
// Why are we re-scaling after writing out the packet?
|
||||
if(mConvertContext == NULL) {
|
||||
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
||||
|
||||
|
@ -349,9 +550,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 +580,11 @@ int RemoteCameraRtsp::Capture( Image &image )
|
|||
|
||||
if(frameComplete)
|
||||
return (0);
|
||||
} // end while true
|
||||
|
||||
}
|
||||
// can never get here.
|
||||
return (0) ;
|
||||
}
|
||||
} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file )
|
||||
|
||||
int RemoteCameraRtsp::PostCapture()
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
};
|
||||
|
||||
#endif // ZM_REMOTE_CAMERA_RTSP_H
|
||||
|
|
|
@ -120,7 +120,7 @@ bool RtpSource::updateSeq( uint16_t seq )
|
|||
{
|
||||
if ( uDelta == 1 )
|
||||
{
|
||||
Debug( 3, "Packet in sequence, gap %d", uDelta );
|
||||
Debug( 4, "Packet in sequence, gap %d", uDelta );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -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 Marker, 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 );
|
||||
|
@ -325,6 +338,9 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
|||
mFrameReady.updateValueSignal( true );
|
||||
if ( !mFrameProcessed.getValueImmediate() )
|
||||
{
|
||||
// What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as
|
||||
// if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false;
|
||||
|
||||
for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ )
|
||||
if( count > 1 )
|
||||
return( false );
|
||||
|
@ -377,7 +393,7 @@ bool RtpSource::getFrame( Buffer &buffer )
|
|||
buffer = mFrame;
|
||||
mFrameReady.setValueImmediate( false );
|
||||
mFrameProcessed.updateValueSignal( true );
|
||||
Debug( 3, "Copied %d bytes", buffer.size() );
|
||||
Debug( 4, "Copied %d bytes", buffer.size() );
|
||||
return( true );
|
||||
}
|
||||
|
||||
|
|
|
@ -50,8 +50,8 @@ Storage::Storage( unsigned int p_id ) {
|
|||
char sql[ZM_SQL_SML_BUFSIZ];
|
||||
snprintf( sql, sizeof(sql), "SELECT Id, Name, Path from Storage WHERE Id=%d", p_id );
|
||||
Debug(1,"Loading Storage for %d using %s", p_id, sql );
|
||||
MYSQL_ROW dbrow = zmDbFetchOne( sql );
|
||||
if ( ! dbrow ) {
|
||||
zmDbRow dbrow;
|
||||
if ( ! dbrow.fetch( sql ) ) {
|
||||
Error( "Unable to load storage area for id %d: %s", p_id, mysql_error( &dbconn ) );
|
||||
} else {
|
||||
unsigned int index = 0;
|
||||
|
|
|
@ -0,0 +1,518 @@
|
|||
// 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"
|
||||
#include "zm_video.h"
|
||||
#include "zm_image.h"
|
||||
#include "zm_utils.h"
|
||||
#include "zm_rgb.h"
|
||||
#include <sstream>
|
||||
|
||||
VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) :
|
||||
container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) {
|
||||
Debug(7,"Video object created");
|
||||
|
||||
/* Parameter checking */
|
||||
if(path.empty()) {
|
||||
Error("Invalid file path");
|
||||
}
|
||||
if(!width || !height) {
|
||||
Error("Invalid width or height");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VideoWriter::~VideoWriter() {
|
||||
Debug(7,"Video object destroyed");
|
||||
|
||||
}
|
||||
|
||||
int VideoWriter::Reset(const char* new_path) {
|
||||
/* Common variables reset */
|
||||
|
||||
/* If there is a new path, use it */
|
||||
if(new_path != NULL) {
|
||||
path = new_path;
|
||||
}
|
||||
|
||||
/* Reset frame counter */
|
||||
frame_count = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if ZM_HAVE_VIDEOWRITER_X264MP4
|
||||
X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<EncoderParameter_t>* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) {
|
||||
|
||||
/* Initialize ffmpeg if it hasn't been initialized yet */
|
||||
FFMPEGInit();
|
||||
|
||||
/* Initialize swscale */
|
||||
zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder);
|
||||
if(zm_pf == 0) {
|
||||
Error("Unable to match ffmpeg pixelformat");
|
||||
}
|
||||
codec_pf = AV_PIX_FMT_YUV420P;
|
||||
|
||||
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
|
||||
|
||||
/* Calculate the image sizes. We will need this for parameter checking */
|
||||
zm_imgsize = colours * width * height;
|
||||
codec_imgsize = avpicture_get_size( codec_pf, width, height);
|
||||
if(!codec_imgsize) {
|
||||
Error("Failed calculating codec pixel format image size");
|
||||
}
|
||||
|
||||
/* If supplied with user parameters to the encoder, copy them */
|
||||
if(p_user_params != NULL) {
|
||||
user_params = *p_user_params;
|
||||
}
|
||||
|
||||
/* Setup x264 parameters */
|
||||
if(x264config() < 0) {
|
||||
Error("Failed setting x264 parameters");
|
||||
}
|
||||
|
||||
/* Allocate x264 input picture */
|
||||
x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height);
|
||||
|
||||
}
|
||||
|
||||
X264MP4Writer::~X264MP4Writer() {
|
||||
|
||||
/* Free x264 input picture */
|
||||
x264_picture_clean(&x264picin);
|
||||
|
||||
if(bOpen)
|
||||
Close();
|
||||
|
||||
}
|
||||
|
||||
int X264MP4Writer::Open() {
|
||||
|
||||
/* Open the encoder */
|
||||
x264enc = x264_encoder_open(&x264params);
|
||||
if(x264enc == NULL) {
|
||||
Error("Failed opening x264 encoder");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc));
|
||||
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
if(!x264_encoder_headers(x264enc,&nals,&i_nals)) {
|
||||
Error("Failed getting encoder headers");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Search SPS NAL for AVC information */
|
||||
for(int i=0;i<i_nals;i++) {
|
||||
if(nals[i].i_type == NAL_SPS) {
|
||||
x264_profleindication = nals[i].p_payload[5];
|
||||
x264_profilecompat = nals[i].p_payload[6];
|
||||
x264_levelindication = nals[i].p_payload[7];
|
||||
bGotH264AVCInfo = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!bGotH264AVCInfo) {
|
||||
Warning("Missing AVC information");
|
||||
}
|
||||
|
||||
/* Create the file */
|
||||
mp4h = MP4Create((path + ".incomplete").c_str());
|
||||
if(mp4h == MP4_INVALID_FILE_HANDLE) {
|
||||
Error("Failed creating mp4 file: %s",path.c_str());
|
||||
return -10;
|
||||
}
|
||||
|
||||
/* Set the global timescale */
|
||||
if(!MP4SetTimeScale(mp4h, 1000)) {
|
||||
Error("Failed setting timescale");
|
||||
return -11;
|
||||
}
|
||||
|
||||
/* Set the global video profile */
|
||||
/* I am a bit confused about this one.
|
||||
I couldn't find what the value should be
|
||||
Some use 0x15 while others use 0x7f. */
|
||||
MP4SetVideoProfileLevel(mp4h, 0x7f);
|
||||
|
||||
/* Add H264 video track */
|
||||
mp4vtid = MP4AddH264VideoTrack(mp4h,1000,MP4_INVALID_DURATION,width,height,x264_profleindication,x264_profilecompat,x264_levelindication,3);
|
||||
if(mp4vtid == MP4_INVALID_TRACK_ID) {
|
||||
Error("Failed adding H264 video track");
|
||||
return -12;
|
||||
}
|
||||
|
||||
bOpen = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Close() {
|
||||
|
||||
/* Flush all pending frames */
|
||||
for(int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
|
||||
x264encodeloop(true);
|
||||
}
|
||||
|
||||
/* Close the encoder */
|
||||
x264_encoder_close(x264enc);
|
||||
|
||||
/* Close MP4 handle */
|
||||
MP4Close(mp4h);
|
||||
|
||||
/* Required for proper HTTP streaming */
|
||||
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
|
||||
|
||||
/* Delete the temporary file */
|
||||
unlink((path + ".incomplete").c_str());
|
||||
|
||||
bOpen = false;
|
||||
|
||||
Debug(7, "Video closed. Total frames: %d", frame_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Reset(const char* new_path) {
|
||||
|
||||
/* Close the encoder and file */
|
||||
if(bOpen)
|
||||
Close();
|
||||
|
||||
/* Reset common variables */
|
||||
VideoWriter::Reset(new_path);
|
||||
|
||||
/* Reset local variables */
|
||||
bFirstFrame = true;
|
||||
bGotH264AVCInfo = false;
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
|
||||
/* Reset x264 parameters */
|
||||
x264config();
|
||||
|
||||
/* Open the encoder */
|
||||
Open();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) {
|
||||
|
||||
/* Parameter checking */
|
||||
if(data == NULL) {
|
||||
Error("NULL buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(data_size != zm_imgsize) {
|
||||
Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(!bOpen) {
|
||||
Warning("The encoder was not initialized, initializing now");
|
||||
Open();
|
||||
}
|
||||
|
||||
/* Convert the image into the x264 input picture */
|
||||
if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) {
|
||||
Error("Image conversion failed");
|
||||
return -3;
|
||||
}
|
||||
|
||||
/* Set PTS */
|
||||
x264picin.i_pts = frame_time;
|
||||
|
||||
/* Do the encoding */
|
||||
x264encodeloop();
|
||||
|
||||
/* Increment frame counter */
|
||||
frame_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
|
||||
|
||||
if(img->Width() != width) {
|
||||
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
|
||||
return -12;
|
||||
}
|
||||
|
||||
if(img->Height() != height) {
|
||||
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
|
||||
return -13;
|
||||
}
|
||||
|
||||
return Encode(img->Buffer(),img->Size(),frame_time);
|
||||
}
|
||||
|
||||
int X264MP4Writer::x264config() {
|
||||
/* Sets up the encoder configuration */
|
||||
|
||||
int x264ret;
|
||||
|
||||
/* Defaults */
|
||||
const char* preset = "veryfast";
|
||||
const char* tune = "stillimage";
|
||||
const char* profile = "main";
|
||||
|
||||
/* Search the user parameters for preset, tune and profile */
|
||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
||||
if(strcmp(user_params[i].pname, "preset") == 0) {
|
||||
/* Got preset */
|
||||
preset = user_params[i].pvalue;
|
||||
} else if(strcmp(user_params[i].pname, "tune") == 0) {
|
||||
/* Got tune */
|
||||
tune = user_params[i].pvalue;
|
||||
} else if(strcmp(user_params[i].pname, "profile") == 0) {
|
||||
/* Got profile */
|
||||
profile = user_params[i].pvalue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the defaults and preset and tune */
|
||||
x264ret = x264_param_default_preset(&x264params, preset, tune);
|
||||
if(x264ret != 0) {
|
||||
Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret);
|
||||
}
|
||||
|
||||
/* Set the profile */
|
||||
x264ret = x264_param_apply_profile(&x264params, profile);
|
||||
if(x264ret != 0) {
|
||||
Error("Failed setting x264 profile %s : %d",profile,x264ret);
|
||||
}
|
||||
|
||||
/* Input format */
|
||||
x264params.i_width = width;
|
||||
x264params.i_height = height;
|
||||
x264params.i_csp = X264_CSP_I420;
|
||||
|
||||
/* Quality control */
|
||||
x264params.rc.i_rc_method = X264_RC_CRF;
|
||||
x264params.rc.f_rf_constant = 23.0;
|
||||
|
||||
/* Enable b-frames */
|
||||
x264params.i_bframe = 16;
|
||||
x264params.i_bframe_adaptive = 1;
|
||||
|
||||
/* Timebase */
|
||||
x264params.i_timebase_num = 1;
|
||||
x264params.i_timebase_den = 1000;
|
||||
|
||||
/* Enable variable frame rate */
|
||||
x264params.b_vfr_input = 1;
|
||||
|
||||
/* Disable annex-b (start codes) */
|
||||
x264params.b_annexb = 0;
|
||||
|
||||
/* TODO: Setup error handler */
|
||||
//x264params.i_log_level = X264_LOG_DEBUG;
|
||||
|
||||
/* Process user parameters (excluding preset, tune and profile) */
|
||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
||||
/* Skip preset, tune and profile */
|
||||
if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pass the name and value to x264 */
|
||||
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
|
||||
|
||||
/* Error checking */
|
||||
if(x264ret != 0) {
|
||||
if(x264ret == X264_PARAM_BAD_NAME) {
|
||||
Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue);
|
||||
} else if(x264ret == X264_PARAM_BAD_VALUE) {
|
||||
Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue);
|
||||
} else {
|
||||
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void X264MP4Writer::x264encodeloop(bool bFlush) {
|
||||
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
int frame_size;
|
||||
|
||||
if(bFlush) {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
|
||||
} else {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
|
||||
}
|
||||
|
||||
if (frame_size > 0 || bFlush) {
|
||||
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
|
||||
|
||||
/* Handle the previous frame */
|
||||
if(!bFirstFrame) {
|
||||
|
||||
buffer.clear();
|
||||
|
||||
/* Process the NALs for the previous frame */
|
||||
for(unsigned int i=0; i < prevnals.size(); i++) {
|
||||
Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload);
|
||||
|
||||
switch(prevnals[i].i_type) {
|
||||
case NAL_PPS:
|
||||
/* PPS NAL */
|
||||
MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
|
||||
break;
|
||||
case NAL_SPS:
|
||||
/* SPS NAL */
|
||||
MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
|
||||
break;
|
||||
default:
|
||||
/* Anything else, hopefully frames, so copy it into the sample */
|
||||
buffer.append(prevnals[i].p_payload, prevnals[i].i_payload);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate frame duration and offset */
|
||||
int duration = x264picout.i_dts - prevDTS;
|
||||
int offset = prevPTS - prevDTS;
|
||||
|
||||
/* Write the sample */
|
||||
if(!buffer.empty()) {
|
||||
if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) {
|
||||
Error("Failed writing sample");
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
|
||||
}
|
||||
|
||||
/* Got a frame. Copy this new frame into the previous frame */
|
||||
if(frame_size > 0) {
|
||||
/* Copy the NALs and the payloads */
|
||||
for(int i=0;i<i_nals;i++) {
|
||||
|
||||
prevnals.push_back(nals[i]);
|
||||
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
|
||||
}
|
||||
|
||||
/* Update the payload pointers */
|
||||
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
|
||||
making the pointers invalid */
|
||||
unsigned int payload_offset = 0;
|
||||
for(unsigned int i=0;i<prevnals.size();i++) {
|
||||
prevnals[i].p_payload = prevpayload.head() + payload_offset;
|
||||
payload_offset += nals[i].i_payload;
|
||||
}
|
||||
|
||||
/* We need this for the next frame */
|
||||
prevPTS = x264picout.i_pts;
|
||||
prevDTS = x264picout.i_dts;
|
||||
prevKeyframe = x264picout.b_keyframe;
|
||||
|
||||
bFirstFrame = false;
|
||||
}
|
||||
|
||||
} else if(frame_size == 0) {
|
||||
Debug(7,"x264 encode returned zero. Delayed frames: %d",x264_encoder_delayed_frames(x264enc));
|
||||
} else {
|
||||
Error("x264 encode failed: %d",frame_size);
|
||||
}
|
||||
}
|
||||
#endif // ZM_VIDEOWRITER_X264MP4
|
||||
|
||||
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec) {
|
||||
if(vec == NULL) {
|
||||
Error("NULL Encoder parameters vector pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(str == NULL) {
|
||||
Error("NULL Encoder parameters string");
|
||||
return -2;
|
||||
}
|
||||
|
||||
vec->clear();
|
||||
|
||||
if(str[0] == 0) {
|
||||
/* Empty */
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::stringstream ss(str);
|
||||
size_t valueoffset;
|
||||
size_t valuelen;
|
||||
unsigned int lineno = 0;
|
||||
EncoderParameter_t param;
|
||||
|
||||
while(std::getline(ss, line) ) {
|
||||
lineno++;
|
||||
|
||||
/* Remove CR if exists */
|
||||
if(line.length() >= 1 && line[line.length()-1] == '\r') {
|
||||
line.erase(line.length()-1);
|
||||
}
|
||||
|
||||
/* Skip comments and empty lines */
|
||||
if(line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
valueoffset = line.find('=');
|
||||
if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) {
|
||||
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(valueoffset > (sizeof(param.pname)-1) ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
|
||||
continue;
|
||||
}
|
||||
|
||||
valuelen = line.length() - (valueoffset+1);
|
||||
|
||||
if( valuelen > (sizeof(param.pvalue)-1) ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Copy and NULL terminate */
|
||||
line.copy(param.pname, valueoffset, 0);
|
||||
line.copy(param.pvalue, valuelen, valueoffset+1);
|
||||
param.pname[valueoffset] = 0;
|
||||
param.pvalue[valuelen] = 0;
|
||||
|
||||
/* Push to the vector */
|
||||
vec->push_back(param);
|
||||
|
||||
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
|
||||
}
|
||||
|
||||
Debug(7, "Parsed %d lines", lineno);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
// 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.
|
||||
//
|
||||
|
||||
#ifndef ZM_VIDEO_H
|
||||
#define ZM_VIDEO_H
|
||||
|
||||
#include "zm.h"
|
||||
#include "zm_rgb.h"
|
||||
#include "zm_utils.h"
|
||||
#include "zm_ffmpeg.h"
|
||||
#include "zm_buffer.h"
|
||||
|
||||
/*
|
||||
#define HAVE_LIBX264 1
|
||||
#define HAVE_LIBMP4V2 1
|
||||
#define HAVE_X264_H 1
|
||||
#define HAVE_MP4_H 1
|
||||
*/
|
||||
|
||||
#if HAVE_MP4V2_MP4V2_H
|
||||
#include <mp4v2/mp4v2.h>
|
||||
#endif
|
||||
#if HAVE_MP4V2_H
|
||||
#include <mp4v2.h>
|
||||
#endif
|
||||
#if HAVE_MP4_H
|
||||
#include <mp4.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_X264_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <x264.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Structure for user parameters to the encoder */
|
||||
struct EncoderParameter_t {
|
||||
char pname[48];
|
||||
char pvalue[48];
|
||||
|
||||
};
|
||||
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec);
|
||||
|
||||
/* VideoWriter is a generic interface that ZM uses to save events as videos */
|
||||
/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */
|
||||
|
||||
class VideoWriter {
|
||||
|
||||
protected:
|
||||
std::string container;
|
||||
std::string codec;
|
||||
std::string path;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int colours;
|
||||
unsigned int subpixelorder;
|
||||
|
||||
unsigned int frame_count;
|
||||
|
||||
public:
|
||||
VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
|
||||
virtual ~VideoWriter();
|
||||
virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0;
|
||||
virtual int Encode(const Image* img, const unsigned int frame_time) = 0;
|
||||
virtual int Open() = 0;
|
||||
virtual int Close() = 0;
|
||||
virtual int Reset(const char* new_path = NULL);
|
||||
|
||||
const char* GetContainer() const {
|
||||
return container.c_str();
|
||||
}
|
||||
const char* GetCodec() const {
|
||||
return codec.c_str();
|
||||
}
|
||||
const char* GetPath() const {
|
||||
return path.c_str();
|
||||
}
|
||||
unsigned int GetWidth() const {
|
||||
return width;
|
||||
}
|
||||
unsigned int GetHeight() const {
|
||||
return height;
|
||||
}
|
||||
unsigned int GetColours() const {
|
||||
return colours;
|
||||
}
|
||||
unsigned int GetSubpixelorder () const {
|
||||
return subpixelorder;
|
||||
}
|
||||
unsigned int GetFrameCount() const {
|
||||
return frame_count;
|
||||
}
|
||||
};
|
||||
|
||||
#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE
|
||||
#define ZM_HAVE_VIDEOWRITER_X264MP4 1
|
||||
class X264MP4Writer : public VideoWriter {
|
||||
|
||||
protected:
|
||||
|
||||
bool bOpen;
|
||||
bool bGotH264AVCInfo;
|
||||
bool bFirstFrame;
|
||||
|
||||
/* SWScale */
|
||||
SWScale swscaleobj;
|
||||
enum _AVPIXELFORMAT zm_pf;
|
||||
enum _AVPIXELFORMAT codec_pf;
|
||||
size_t codec_imgsize;
|
||||
size_t zm_imgsize;
|
||||
|
||||
/* User parameters */
|
||||
std::vector<EncoderParameter_t> user_params;
|
||||
|
||||
/* AVC Information */
|
||||
uint8_t x264_profleindication;
|
||||
uint8_t x264_profilecompat;
|
||||
uint8_t x264_levelindication;
|
||||
|
||||
/* NALs */
|
||||
Buffer buffer;
|
||||
|
||||
/* Previous frame */
|
||||
int prevPTS;
|
||||
int prevDTS;
|
||||
bool prevKeyframe;
|
||||
Buffer prevpayload;
|
||||
std::vector<x264_nal_t> prevnals;
|
||||
|
||||
/* Internal functions */
|
||||
int x264config();
|
||||
void x264encodeloop(bool bFlush = false);
|
||||
|
||||
/* x264 objects */
|
||||
x264_t* x264enc;
|
||||
x264_param_t x264params;
|
||||
x264_picture_t x264picin;
|
||||
x264_picture_t x264picout;
|
||||
|
||||
/* MP4v2 objects */
|
||||
MP4FileHandle mp4h;
|
||||
MP4TrackId mp4vtid;
|
||||
|
||||
|
||||
public:
|
||||
X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<EncoderParameter_t>* p_user_params = NULL);
|
||||
~X264MP4Writer();
|
||||
int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time);
|
||||
int Encode(const Image* img, const unsigned int frame_time);
|
||||
int Open();
|
||||
int Close();
|
||||
int Reset(const char* new_path = NULL);
|
||||
|
||||
};
|
||||
#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE
|
||||
|
||||
#endif // ZM_VIDEO_H
|
|
@ -0,0 +1,349 @@
|
|||
//
|
||||
// ZoneMinder Video Storage Implementation
|
||||
// Written by Chris Wiggins
|
||||
// http://chriswiggins.co.nz
|
||||
// Modification by Steve Gilvarry
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#define __STDC_FORMAT_MACROS 1
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "zm.h"
|
||||
#include "zm_videostore.h"
|
||||
|
||||
extern "C"{
|
||||
#include "libavutil/time.h"
|
||||
}
|
||||
|
||||
VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||
AVStream *input_st,
|
||||
AVStream *inpaud_st,
|
||||
int64_t nStartTime) {
|
||||
|
||||
AVDictionary *pmetadata = NULL;
|
||||
int dsr;
|
||||
|
||||
//store inputs in variables local to class
|
||||
filename = filename_in;
|
||||
format = format_in;
|
||||
|
||||
keyframeMessage = false;
|
||||
keyframeSkipNumber = 0;
|
||||
|
||||
Info("Opening video storage stream %s format: %d\n", filename, format);
|
||||
|
||||
//Init everything we need
|
||||
int ret;
|
||||
av_register_all();
|
||||
|
||||
ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename);
|
||||
if ( ret < 0 ) {
|
||||
Warning("Could not create video storage stream %s as no output context"
|
||||
" could be assigned based on filename: %s",
|
||||
filename,
|
||||
av_make_error_string(ret).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
//Couldn't deduce format from filename, trying from format name
|
||||
if (!oc) {
|
||||
avformat_alloc_output_context2(&oc, NULL, format, filename);
|
||||
if (!oc) {
|
||||
Fatal("Could not create video storage stream %s as no output context"
|
||||
" could not be assigned based on filename or format %s",
|
||||
filename, format);
|
||||
}
|
||||
}
|
||||
|
||||
dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0);
|
||||
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
||||
|
||||
oc->metadata = pmetadata;
|
||||
|
||||
fmt = oc->oformat;
|
||||
|
||||
video_st = avformat_new_stream(oc, (AVCodec *)input_st->codec->codec);
|
||||
if (!video_st) {
|
||||
Fatal("Unable to create video out stream\n");
|
||||
}
|
||||
|
||||
ret = avcodec_copy_context(video_st->codec, input_st->codec);
|
||||
if (ret < 0) {
|
||||
Fatal("Unable to copy input video context to output video context %s\n",
|
||||
av_make_error_string(ret).c_str());
|
||||
}
|
||||
|
||||
video_st->codec->codec_tag = 0;
|
||||
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
video_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
if (inpaud_st) {
|
||||
|
||||
audio_st = avformat_new_stream(oc, (AVCodec *)inpaud_st->codec->codec);
|
||||
if (!audio_st) {
|
||||
Error("Unable to create audio out stream\n");
|
||||
audio_st = NULL;
|
||||
} else {
|
||||
ret = avcodec_copy_context(audio_st->codec, inpaud_st->codec);
|
||||
if (ret < 0) {
|
||||
Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str());
|
||||
}
|
||||
audio_st->codec->codec_tag = 0;
|
||||
if ( audio_st->codec->channels > 1 ) {
|
||||
Warning("Audio isn't mono, changing it.");
|
||||
audio_st->codec->channels = 1;
|
||||
}
|
||||
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||
audio_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Debug(3, "No Audio output stream");
|
||||
audio_st = NULL;
|
||||
}
|
||||
|
||||
/* open the output file, if needed */
|
||||
if (!(fmt->flags & AVFMT_NOFILE)) {
|
||||
ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL);
|
||||
if (ret < 0) {
|
||||
Fatal("Could not open output file '%s': %s\n", filename,
|
||||
av_make_error_string(ret).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
|
||||
//if ((ret = avformat_write_header(ctx, &opts)) < 0) {
|
||||
//}
|
||||
//os->ctx_inited = 1;
|
||||
//avio_flush(ctx->pb);
|
||||
//av_dict_free(&opts);
|
||||
|
||||
/* Write the stream header, if any. */
|
||||
ret = avformat_write_header(oc, NULL);
|
||||
if (ret < 0) {
|
||||
zm_dump_stream_format( oc, 0, 0, 1 );
|
||||
Fatal("Error occurred when writing output file header to %s: %s\n",
|
||||
filename,
|
||||
av_make_error_string(ret).c_str());
|
||||
}
|
||||
|
||||
prevDts = 0;
|
||||
startPts = 0;
|
||||
startDts = 0;
|
||||
filter_in_rescale_delta_last = AV_NOPTS_VALUE;
|
||||
|
||||
startTime=av_gettime()-nStartTime;//oc->start_time;
|
||||
Info("VideoStore startTime=%d\n",startTime);
|
||||
} // VideoStore::VideoStore
|
||||
|
||||
|
||||
VideoStore::~VideoStore(){
|
||||
/* Write the trailer before close */
|
||||
if ( int rc = av_write_trailer(oc) ) {
|
||||
Error("Error writing trailer %s", av_err2str( rc ) );
|
||||
} else {
|
||||
Debug(3, "Sucess Writing trailer");
|
||||
}
|
||||
|
||||
// I wonder if we should be closing the file first.
|
||||
// I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc.
|
||||
// What if we were only doing audio recording?
|
||||
if ( video_st ) {
|
||||
avcodec_close(video_st->codec);
|
||||
}
|
||||
if (audio_st) {
|
||||
avcodec_close(audio_st->codec);
|
||||
}
|
||||
|
||||
// WHen will be not using a file ?
|
||||
if (!(fmt->flags & AVFMT_NOFILE)) {
|
||||
/* Close the output file. */
|
||||
if ( int rc = avio_close(oc->pb) ) {
|
||||
Error("Error closing avio %s", av_err2str( rc ) );
|
||||
}
|
||||
} else {
|
||||
Debug(3, "Not closing avio because we are not writing to a file.");
|
||||
}
|
||||
|
||||
/* free the stream */
|
||||
avformat_free_context(oc);
|
||||
}
|
||||
|
||||
|
||||
void VideoStore::dumpPacket( AVPacket *pkt ){
|
||||
char b[10240];
|
||||
|
||||
snprintf(b, sizeof(b), " pts: %" PRId64 ", dts: %" PRId64 ", data: %p, size: %d, sindex: %d, dflags: %04x, s-pos: %" PRId64 ", c-duration: %" PRId64 "\n"
|
||||
, pkt->pts
|
||||
, pkt->dts
|
||||
, pkt->data
|
||||
, pkt->size
|
||||
, pkt->stream_index
|
||||
, pkt->flags
|
||||
, pkt->pos
|
||||
, pkt->convergence_duration
|
||||
);
|
||||
Info("%s:%d:DEBUG: %s", __FILE__, __LINE__, b);
|
||||
}
|
||||
|
||||
int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AVPacket *lastKeyframePkt){
|
||||
|
||||
int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, video_st->time_base);
|
||||
|
||||
AVPacket opkt, safepkt;
|
||||
AVPicture pict;
|
||||
|
||||
av_init_packet(&opkt);
|
||||
|
||||
//Scale the PTS of the outgoing packet to be the correct time base
|
||||
if (ipkt->pts != AV_NOPTS_VALUE) {
|
||||
opkt.pts = av_rescale_q(ipkt->pts-startPts, input_st->time_base, video_st->time_base) - ost_tb_start_time;
|
||||
} else {
|
||||
opkt.pts = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
//Scale the DTS of the outgoing packet to be the correct time base
|
||||
if(ipkt->dts == AV_NOPTS_VALUE) {
|
||||
opkt.dts = av_rescale_q(input_st->cur_dts-startDts, AV_TIME_BASE_Q, video_st->time_base);
|
||||
} else {
|
||||
opkt.dts = av_rescale_q(ipkt->dts-startDts, input_st->time_base, video_st->time_base);
|
||||
}
|
||||
|
||||
opkt.dts -= ost_tb_start_time;
|
||||
|
||||
opkt.duration = av_rescale_q(ipkt->duration, input_st->time_base, video_st->time_base);
|
||||
opkt.flags = ipkt->flags;
|
||||
opkt.pos=-1;
|
||||
|
||||
opkt.data = ipkt->data;
|
||||
opkt.size = ipkt->size;
|
||||
opkt.stream_index = ipkt->stream_index;
|
||||
/*opkt.flags |= AV_PKT_FLAG_KEY;*/
|
||||
|
||||
if (video_st->codec->codec_type == AVMEDIA_TYPE_VIDEO && (fmt->flags & AVFMT_RAWPICTURE)) {
|
||||
/* store AVPicture in AVPacket, as expected by the output format */
|
||||
avpicture_fill(&pict, opkt.data, video_st->codec->pix_fmt, video_st->codec->width, video_st->codec->height);
|
||||
opkt.data = (uint8_t *)&pict;
|
||||
opkt.size = sizeof(AVPicture);
|
||||
opkt.flags |= AV_PKT_FLAG_KEY;
|
||||
}
|
||||
|
||||
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
||||
|
||||
if ((opkt.data == NULL)||(opkt.size < 1)) {
|
||||
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
||||
dumpPacket(&opkt);
|
||||
|
||||
} else if ((prevDts > 0) && (prevDts >= opkt.dts)) {
|
||||
Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, prevDts, opkt.dts);
|
||||
prevDts = opkt.dts;
|
||||
dumpPacket(&opkt);
|
||||
|
||||
} else {
|
||||
int ret;
|
||||
|
||||
prevDts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance
|
||||
ret = av_interleaved_write_frame(oc, &opkt);
|
||||
if(ret<0){
|
||||
// There's nothing we can really do if the frame is rejected, just drop it and get on with the next
|
||||
Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret));
|
||||
dumpPacket(&safepkt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
av_free_packet(&opkt);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int VideoStore::writeAudioFramePacket(AVPacket *ipkt, AVStream *input_st){
|
||||
|
||||
if(!audio_st) {
|
||||
Error("Called writeAudioFramePacket when no audio_st");
|
||||
return -1;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment
|
||||
}
|
||||
/*if(!keyframeMessage)
|
||||
return -1;*/
|
||||
//zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 );
|
||||
|
||||
// What is this doing? Getting the time of the start of this video chunk? Does that actually make sense?
|
||||
int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, audio_st->time_base);
|
||||
|
||||
AVPacket opkt;
|
||||
|
||||
av_init_packet(&opkt);
|
||||
Debug(3, "after init packet" );
|
||||
|
||||
|
||||
//Scale the PTS of the outgoing packet to be the correct time base
|
||||
if (ipkt->pts != AV_NOPTS_VALUE) {
|
||||
Debug(3, "Rescaling output pts");
|
||||
opkt.pts = av_rescale_q(ipkt->pts-startPts, input_st->time_base, audio_st->time_base) - ost_tb_start_time;
|
||||
} else {
|
||||
Debug(3, "Setting output pts to AV_NOPTS_VALUE");
|
||||
opkt.pts = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
//Scale the DTS of the outgoing packet to be the correct time base
|
||||
if(ipkt->dts == AV_NOPTS_VALUE) {
|
||||
Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
||||
opkt.dts = av_rescale_q(input_st->cur_dts-startDts, AV_TIME_BASE_Q, audio_st->time_base);
|
||||
Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
||||
} else {
|
||||
Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
||||
opkt.dts = av_rescale_q(ipkt->dts-startDts, input_st->time_base, audio_st->time_base);
|
||||
Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
||||
}
|
||||
opkt.dts -= ost_tb_start_time;
|
||||
|
||||
// Seems like it would be really weird for the codec type to NOT be audiu
|
||||
if (audio_st->codec->codec_type == AVMEDIA_TYPE_AUDIO && ipkt->dts != AV_NOPTS_VALUE) {
|
||||
Debug( 4, "code is audio, dts != AV_NOPTS_VALUE " );
|
||||
int duration = av_get_audio_frame_duration(input_st->codec, ipkt->size);
|
||||
if(!duration)
|
||||
duration = input_st->codec->frame_size;
|
||||
|
||||
//FIXME where to get filter_in_rescale_delta_last
|
||||
//FIXME av_rescale_delta doesn't exist in ubuntu vivid libavtools
|
||||
opkt.dts = opkt.pts = av_rescale_delta(input_st->time_base, ipkt->dts,
|
||||
(AVRational){1, input_st->codec->sample_rate}, duration, &filter_in_rescale_delta_last,
|
||||
audio_st->time_base) - ost_tb_start_time;
|
||||
}
|
||||
|
||||
opkt.duration = av_rescale_q(ipkt->duration, input_st->time_base, audio_st->time_base);
|
||||
opkt.pos=-1;
|
||||
opkt.flags = ipkt->flags;
|
||||
|
||||
opkt.data = ipkt->data;
|
||||
opkt.size = ipkt->size;
|
||||
opkt.stream_index = ipkt->stream_index;
|
||||
|
||||
int ret;
|
||||
ret = av_interleaved_write_frame(oc, &opkt);
|
||||
if(ret!=0){
|
||||
Fatal("Error encoding audio frame packet: %s\n", av_make_error_string(ret).c_str());
|
||||
}
|
||||
Debug(4,"Success writing audio frame" );
|
||||
av_free_packet(&opkt);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef ZM_VIDEOSTORE_H
|
||||
#define ZM_VIDEOSTORE_H
|
||||
|
||||
#include "zm_ffmpeg.h"
|
||||
|
||||
#if HAVE_LIBAVCODEC
|
||||
|
||||
class VideoStore {
|
||||
private:
|
||||
|
||||
AVOutputFormat *fmt;
|
||||
AVFormatContext *oc;
|
||||
AVStream *video_st;
|
||||
AVStream *audio_st;
|
||||
|
||||
const char *filename;
|
||||
const char *format;
|
||||
|
||||
bool keyframeMessage;
|
||||
int keyframeSkipNumber;
|
||||
|
||||
int64_t startTime;
|
||||
int64_t startPts;
|
||||
int64_t startDts;
|
||||
int64_t prevDts;
|
||||
int64_t filter_in_rescale_delta_last;
|
||||
|
||||
public:
|
||||
VideoStore(const char *filename_in, const char *format_in, AVStream *input_st, AVStream *inpaud_st, int64_t nStartTime);
|
||||
~VideoStore();
|
||||
|
||||
int writeVideoFramePacket(AVPacket *pkt, AVStream *input_st);//, AVPacket *lastKeyframePkt);
|
||||
int writeAudioFramePacket(AVPacket *pkt, AVStream *input_st);
|
||||
void dumpPacket( AVPacket *pkt );
|
||||
};
|
||||
|
||||
/*
|
||||
class VideoEvent {
|
||||
public:
|
||||
VideoEvent(unsigned int eid);
|
||||
~VideoEvent();
|
||||
|
||||
int createEventImage(unsigned int fid, char *&pBuff);
|
||||
|
||||
private:
|
||||
unsigned int m_eid;
|
||||
};*/
|
||||
|
||||
#endif //havelibav
|
||||
#endif //zm_videostore_h
|
||||
|
533
src/zm_zone.cpp
533
src/zm_zone.cpp
|
@ -25,7 +25,6 @@
|
|||
|
||||
void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_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, int p_overload_frames, int p_extend_alarm_frames )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
monitor = p_monitor;
|
||||
|
||||
id = p_id;
|
||||
|
@ -102,84 +101,6 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_
|
|||
}
|
||||
pg_image->WriteJpeg( diag_path );
|
||||
}
|
||||
=======
|
||||
monitor = p_monitor;
|
||||
|
||||
id = p_id;
|
||||
label = new char[strlen(p_label)+1];
|
||||
strcpy( label, p_label );
|
||||
type = p_type;
|
||||
polygon = p_polygon;
|
||||
alarm_rgb = p_alarm_rgb;
|
||||
check_method = p_check_method;
|
||||
min_pixel_threshold = p_min_pixel_threshold;
|
||||
max_pixel_threshold = p_max_pixel_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;
|
||||
overload_frames = p_overload_frames;
|
||||
extend_alarm_frames = p_extend_alarm_frames;
|
||||
|
||||
Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_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, overload_frames, extend_alarm_frames );
|
||||
|
||||
alarmed = false;
|
||||
pixel_diff = 0;
|
||||
alarm_pixels = 0;
|
||||
alarm_filter_pixels = 0;
|
||||
alarm_blob_pixels = 0;
|
||||
alarm_blobs = 0;
|
||||
min_blob_size = 0;
|
||||
max_blob_size = 0;
|
||||
image = 0;
|
||||
score = 0;
|
||||
|
||||
overload_count = 0;
|
||||
extend_alarm_count = 0;
|
||||
|
||||
pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE);
|
||||
pg_image->Clear();
|
||||
pg_image->Fill( 0xff, polygon );
|
||||
pg_image->Outline( 0xff, polygon );
|
||||
|
||||
ranges = new Range[monitor->Height()];
|
||||
for ( unsigned int y = 0; y < monitor->Height(); y++)
|
||||
{
|
||||
ranges[y].lo_x = -1;
|
||||
ranges[y].hi_x = 0;
|
||||
ranges[y].off_x = 0;
|
||||
const uint8_t *ppoly = pg_image->Buffer( 0, y );
|
||||
for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ )
|
||||
{
|
||||
if ( *ppoly )
|
||||
{
|
||||
if ( ranges[y].lo_x == -1 )
|
||||
{
|
||||
ranges[y].lo_x = x;
|
||||
}
|
||||
if ( (unsigned int)ranges[y].hi_x < x )
|
||||
{
|
||||
ranges[y].hi_x = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( config.record_diag_images )
|
||||
{
|
||||
static char diag_path[PATH_MAX] = "";
|
||||
if ( !diag_path[0] )
|
||||
{
|
||||
snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-poly.jpg", config.dir_events, monitor->Name(), id);
|
||||
}
|
||||
pg_image->WriteJpeg( diag_path );
|
||||
}
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
Zone::~Zone()
|
||||
|
@ -277,7 +198,6 @@ bool Zone::CheckExtendAlarmCount()
|
|||
|
||||
bool Zone::CheckAlarms( const Image *delta_image )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
ResetStats();
|
||||
|
||||
if ( overload_count )
|
||||
|
@ -730,459 +650,6 @@ bool Zone::CheckAlarms( const Image *delta_image )
|
|||
diff_image->WriteJpeg( diag_path );
|
||||
}
|
||||
Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs );
|
||||
=======
|
||||
ResetStats();
|
||||
|
||||
if ( overload_count )
|
||||
{
|
||||
Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames );
|
||||
Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames );
|
||||
overload_count--;
|
||||
return( false );
|
||||
}
|
||||
|
||||
delete image;
|
||||
// Get the difference image
|
||||
Image *diff_image = image = new Image( *delta_image );
|
||||
int diff_width = diff_image->Width();
|
||||
uint8_t* diff_buff = (uint8_t*)diff_image->Buffer();
|
||||
uint8_t* pdiff;
|
||||
const uint8_t* ppoly;
|
||||
|
||||
unsigned int pixel_diff_count = 0;
|
||||
|
||||
int alarm_lo_x = 0;
|
||||
int alarm_hi_x = 0;
|
||||
int alarm_lo_y = 0;
|
||||
int alarm_hi_y = 0;
|
||||
|
||||
int alarm_mid_x = -1;
|
||||
int alarm_mid_y = -1;
|
||||
|
||||
unsigned int lo_y = polygon.LoY();
|
||||
unsigned int lo_x = polygon.LoX();
|
||||
unsigned int hi_x = polygon.HiX();
|
||||
unsigned int hi_y = polygon.HiY();
|
||||
|
||||
Debug( 4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y );
|
||||
|
||||
|
||||
Debug( 5, "Checking for alarmed pixels" );
|
||||
/* if(config.cpu_extensions && sseversion >= 20) {
|
||||
sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
|
||||
} else {
|
||||
std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
|
||||
} */
|
||||
std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
|
||||
|
||||
if ( config.record_diag_images )
|
||||
{
|
||||
static char diag_path[PATH_MAX] = "";
|
||||
if ( !diag_path[0] )
|
||||
{
|
||||
snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-%d.jpg", config.dir_events, monitor->Name(), id, 1 );
|
||||
}
|
||||
diff_image->WriteJpeg( diag_path );
|
||||
}
|
||||
|
||||
if ( pixel_diff_count && alarm_pixels )
|
||||
pixel_diff = pixel_diff_count/alarm_pixels;
|
||||
Debug( 5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff );
|
||||
|
||||
if( alarm_pixels ) {
|
||||
if( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) {
|
||||
/* Not enough pixels alarmed */
|
||||
return (false);
|
||||
} else if( max_alarm_pixels && (alarm_pixels > (unsigned int)max_alarm_pixels) ) {
|
||||
/* Too many pixels alarmed */
|
||||
overload_count = overload_frames;
|
||||
return (false);
|
||||
}
|
||||
} else {
|
||||
/* No alarmed pixels */
|
||||
return (false);
|
||||
}
|
||||
|
||||
score = (100*alarm_pixels)/polygon.Area();
|
||||
if(score < 1)
|
||||
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
|
||||
Debug( 5, "Current score is %d", score );
|
||||
|
||||
if ( check_method >= FILTERED_PIXELS )
|
||||
{
|
||||
int bx = filter_box.X();
|
||||
int by = filter_box.Y();
|
||||
int bx1 = bx-1;
|
||||
int by1 = by-1;
|
||||
|
||||
Debug( 5, "Checking for filtered pixels" );
|
||||
if ( bx > 1 || by > 1 )
|
||||
{
|
||||
// Now remove any pixels smaller than our filter size
|
||||
unsigned char *cpdiff;
|
||||
int ldx, hdx, ldy, hdy;
|
||||
bool block;
|
||||
for ( unsigned int y = lo_y; y <= hi_y; y++ )
|
||||
{
|
||||
int lo_x = ranges[y].lo_x;
|
||||
int hi_x = ranges[y].hi_x;
|
||||
|
||||
pdiff = (uint8_t*)diff_image->Buffer( lo_x, y );
|
||||
|
||||
for ( int x = lo_x; x <= hi_x; x++, pdiff++ )
|
||||
{
|
||||
if ( *pdiff == WHITE )
|
||||
{
|
||||
// Check participation in an X block
|
||||
ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x;
|
||||
hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1);
|
||||
ldy = (y>=(lo_y+by1))?-by1:lo_y-y;
|
||||
hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1);
|
||||
block = false;
|
||||
for ( int dy = ldy; !block && dy <= hdy; dy++ )
|
||||
{
|
||||
for ( int dx = ldx; !block && dx <= hdx; dx++ )
|
||||
{
|
||||
block = true;
|
||||
for ( int dy2 = 0; block && dy2 < by; dy2++ )
|
||||
{
|
||||
for ( int dx2 = 0; block && dx2 < bx; dx2++ )
|
||||
{
|
||||
cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2));
|
||||
if ( !*cpdiff )
|
||||
{
|
||||
block = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !block )
|
||||
{
|
||||
*pdiff = BLACK;
|
||||
continue;
|
||||
}
|
||||
alarm_filter_pixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
alarm_filter_pixels = alarm_pixels;
|
||||
}
|
||||
|
||||
if ( config.record_diag_images )
|
||||
{
|
||||
static char diag_path[PATH_MAX] = "";
|
||||
if ( !diag_path[0] )
|
||||
{
|
||||
snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 2 );
|
||||
}
|
||||
diff_image->WriteJpeg( diag_path );
|
||||
}
|
||||
|
||||
Debug( 5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels );
|
||||
|
||||
if( alarm_filter_pixels ) {
|
||||
if( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) {
|
||||
/* Not enough pixels alarmed */
|
||||
return (false);
|
||||
} else if( max_filter_pixels && (alarm_filter_pixels > max_filter_pixels) ) {
|
||||
/* Too many pixels alarmed */
|
||||
overload_count = overload_frames;
|
||||
return (false);
|
||||
}
|
||||
} else {
|
||||
/* No filtered pixels */
|
||||
return (false);
|
||||
}
|
||||
|
||||
score = (100*alarm_filter_pixels)/(polygon.Area());
|
||||
if(score < 1)
|
||||
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
|
||||
Debug( 5, "Current score is %d", score );
|
||||
|
||||
if ( check_method >= BLOBS )
|
||||
{
|
||||
Debug( 5, "Checking for blob pixels" );
|
||||
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 );
|
||||
uint8_t *spdiff;
|
||||
uint8_t last_x, last_y;
|
||||
BlobStats *bsx, *bsy;
|
||||
BlobStats *bsm, *bss;
|
||||
for ( unsigned int y = lo_y; y <= hi_y; y++ )
|
||||
{
|
||||
int lo_x = ranges[y].lo_x;
|
||||
int hi_x = ranges[y].hi_x;
|
||||
|
||||
pdiff = (uint8_t*)diff_image->Buffer( lo_x, y );
|
||||
for ( int x = lo_x; x <= hi_x; x++, pdiff++ )
|
||||
{
|
||||
if ( *pdiff == WHITE )
|
||||
{
|
||||
Debug( 9, "Got white pixel at %d,%d (%p)", x, y, pdiff );
|
||||
//last_x = (x>lo_x)?*(pdiff-1):0;
|
||||
//last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0;
|
||||
|
||||
last_x = 0;
|
||||
if(x > 0) {
|
||||
if((x-1) >= lo_x) {
|
||||
last_x = *(pdiff-1);
|
||||
}
|
||||
}
|
||||
|
||||
last_y = 0;
|
||||
if(y > 0) {
|
||||
if((y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x) {
|
||||
last_y = *(pdiff-diff_width);
|
||||
}
|
||||
}
|
||||
>>>>>>> master
|
||||
|
||||
if ( last_x )
|
||||
{
|
||||
Debug( 9, "Left neighbour is %d", last_x );
|
||||
bsx = &blob_stats[last_x];
|
||||
if ( last_y )
|
||||
{
|
||||
Debug( 9, "Top neighbour is %d", last_y );
|
||||
bsy = &blob_stats[last_y];
|
||||
if ( last_x == last_y )
|
||||
{
|
||||
Debug( 9, "Matching neighbours, setting to %d", last_x );
|
||||
// Add to the blob from the x side (either side really)
|
||||
*pdiff = last_x;
|
||||
alarm_blob_pixels++;
|
||||
bsx->count++;
|
||||
if ( x > bsx->hi_x ) bsx->hi_x = x;
|
||||
if ( (int)y > bsx->hi_y ) bsx->hi_y = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Aggregate blobs
|
||||
bsm = bsx->count>=bsy->count?bsx:bsy;
|
||||
bss = bsm==bsx?bsy:bsx;
|
||||
|
||||
Debug( 9, "Different neighbours, setting pixels of %d to %d", bss->tag, bsm->tag );
|
||||
Debug( 9, "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y );
|
||||
Debug( 9, "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y );
|
||||
// Now change all those pixels to the other setting
|
||||
int changed = 0;
|
||||
for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++)
|
||||
{
|
||||
int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x;
|
||||
int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x;
|
||||
|
||||
Debug( 9, "Changing %d, %d->%d", sy, lo_sx, hi_sx );
|
||||
Debug( 9, "Range %d, %d->%d", sy, ranges[sy].lo_x, ranges[sy].hi_x );
|
||||
spdiff = diff_buff + ((diff_width * sy) + lo_sx);
|
||||
for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ )
|
||||
{
|
||||
Debug( 9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff );
|
||||
if ( *spdiff == bss->tag )
|
||||
{
|
||||
Debug( 9, "Setting pixel" );
|
||||
*spdiff = bsm->tag;
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
*pdiff = bsm->tag;
|
||||
alarm_blob_pixels++;
|
||||
if ( !changed )
|
||||
{
|
||||
Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y );
|
||||
Info( "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y );
|
||||
Error( "No pixels changed, exiting" );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
// Merge the slave blob into the master
|
||||
bsm->count += bss->count+1;
|
||||
if ( x > bsm->hi_x ) bsm->hi_x = x;
|
||||
if ( (int)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;
|
||||
|
||||
alarm_blobs--;
|
||||
|
||||
Debug( 6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs );
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug( 9, "Setting to left neighbour %d", last_x );
|
||||
// Add to the blob from the x side
|
||||
*pdiff = last_x;
|
||||
alarm_blob_pixels++;
|
||||
bsx->count++;
|
||||
if ( x > bsx->hi_x ) bsx->hi_x = x;
|
||||
if ( (int)y > bsx->hi_y ) bsx->hi_y = y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( last_y )
|
||||
{
|
||||
Debug( 9, "Top neighbour is %d", last_y );
|
||||
Debug( 9, "Setting to top neighbour %d", last_y );
|
||||
|
||||
// Add to the blob from the y side
|
||||
BlobStats *bsy = &blob_stats[last_y];
|
||||
|
||||
*pdiff = last_y;
|
||||
alarm_blob_pixels++;
|
||||
bsy->count++;
|
||||
if ( x > bsy->hi_x ) bsy->hi_x = x;
|
||||
if ( (int)y > bsy->hi_y ) bsy->hi_y = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new blob
|
||||
int i;
|
||||
for ( i = (WHITE-1); i > 0; i-- )
|
||||
{
|
||||
BlobStats *bs = &blob_stats[i];
|
||||
// See if we can recycle one first, only if it's at least two rows up
|
||||
if ( bs->count && bs->hi_y < (int)(y-1) )
|
||||
{
|
||||
if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) )
|
||||
{
|
||||
if ( config.create_analysis_images || config.record_diag_images )
|
||||
{
|
||||
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ )
|
||||
{
|
||||
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
|
||||
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ )
|
||||
{
|
||||
if ( *spdiff == bs->tag )
|
||||
{
|
||||
*spdiff = BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
alarm_blobs--;
|
||||
alarm_blob_pixels -= bs->count;
|
||||
|
||||
Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs );
|
||||
|
||||
bs->tag = 0;
|
||||
bs->count = 0;
|
||||
bs->lo_x = 0;
|
||||
bs->lo_y = 0;
|
||||
bs->hi_x = 0;
|
||||
bs->hi_y = 0;
|
||||
}
|
||||
}
|
||||
if ( !bs->count )
|
||||
{
|
||||
Debug( 9, "Creating new blob %d", i );
|
||||
*pdiff = i;
|
||||
alarm_blob_pixels++;
|
||||
bs->tag = i;
|
||||
bs->count++;
|
||||
bs->lo_x = bs->hi_x = x;
|
||||
bs->lo_y = bs->hi_y = y;
|
||||
alarm_blobs++;
|
||||
|
||||
Debug( 6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, alarm_blobs );
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i == 0 )
|
||||
{
|
||||
Warning( "Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive." );
|
||||
x = hi_x+1;
|
||||
y = hi_y+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( config.record_diag_images )
|
||||
{
|
||||
static char diag_path[PATH_MAX] = "";
|
||||
if ( !diag_path[0] )
|
||||
{
|
||||
snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 3 );
|
||||
}
|
||||
diff_image->WriteJpeg( diag_path );
|
||||
}
|
||||
|
||||
if ( !alarm_blobs )
|
||||
{
|
||||
return( false );
|
||||
}
|
||||
|
||||
Debug( 5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs );
|
||||
|
||||
// Now eliminate blobs under the threshold
|
||||
for ( int i = 1; i < WHITE; i++ )
|
||||
{
|
||||
BlobStats *bs = &blob_stats[i];
|
||||
if ( bs->count )
|
||||
{
|
||||
if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) )
|
||||
{
|
||||
if ( config.create_analysis_images || config.record_diag_images )
|
||||
{
|
||||
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ )
|
||||
{
|
||||
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
|
||||
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ )
|
||||
{
|
||||
if ( *spdiff == bs->tag )
|
||||
{
|
||||
*spdiff = BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
alarm_blobs--;
|
||||
alarm_blob_pixels -= bs->count;
|
||||
|
||||
Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs );
|
||||
|
||||
bs->tag = 0;
|
||||
bs->count = 0;
|
||||
bs->lo_x = 0;
|
||||
bs->lo_y = 0;
|
||||
bs->hi_x = 0;
|
||||
bs->hi_y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug( 6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs );
|
||||
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 ( config.record_diag_images )
|
||||
{
|
||||
static char diag_path[PATH_MAX] = "";
|
||||
if ( !diag_path[0] )
|
||||
{
|
||||
snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 4 );
|
||||
}
|
||||
diff_image->WriteJpeg( diag_path );
|
||||
}
|
||||
Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs );
|
||||
|
||||
if( alarm_blobs ) {
|
||||
if( min_blobs && (alarm_blobs < min_blobs) ) {
|
||||
|
|
165
src/zmf.cpp
165
src/zmf.cpp
|
@ -138,7 +138,6 @@ void Usage()
|
|||
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
self = argv[0];
|
||||
|
||||
srand( getpid() * time( 0 ) );
|
||||
|
@ -303,172 +302,8 @@ int main( int argc, char *argv[] )
|
|||
|
||||
// Print errors if there was a problem
|
||||
if ( n_bytes < 1 )
|
||||
=======
|
||||
self = argv[0];
|
||||
|
||||
srand( getpid() * time( 0 ) );
|
||||
|
||||
int id = -1;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"monitor", 1, 0, 'm'},
|
||||
{"help", 0, 0, 'h'},
|
||||
{"version", 0, 0, 'v'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
while (1)
|
||||
{
|
||||
int option_index = 0;
|
||||
|
||||
int c = getopt_long (argc, argv, "m:h:v", long_options, &option_index);
|
||||
if (c == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 'm':
|
||||
id = atoi(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
Usage();
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << ZM_VERSION << "\n";
|
||||
exit(0);
|
||||
default:
|
||||
//fprintf( stderr, "?? getopt returned character code 0%o ??\n", c );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
{
|
||||
fprintf( stderr, "Extraneous options, " );
|
||||
while (optind < argc)
|
||||
printf ("%s ", argv[optind++]);
|
||||
printf ("\n");
|
||||
Usage();
|
||||
}
|
||||
|
||||
if ( id < 0 )
|
||||
{
|
||||
fprintf( stderr, "Bogus monitor %d\n", id );
|
||||
Usage();
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
char log_id_string[16];
|
||||
snprintf( log_id_string, sizeof(log_id_string), "m%d", id );
|
||||
|
||||
zmLoadConfig();
|
||||
|
||||
logInit( "zmf" );
|
||||
|
||||
ssedetect();
|
||||
|
||||
Monitor *monitor = Monitor::Load( id, false, Monitor::QUERY );
|
||||
|
||||
if ( !monitor )
|
||||
{
|
||||
fprintf( stderr, "Can't find monitor with id of %d\n", id );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
char capt_path[PATH_MAX];
|
||||
char anal_path[PATH_MAX];
|
||||
snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", config.dir_events, monitor->Id(), config.event_image_digits );
|
||||
snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", config.dir_events, monitor->Id(), config.event_image_digits );
|
||||
zmSetDefaultTermHandler();
|
||||
zmSetDefaultDieHandler();
|
||||
|
||||
sigset_t block_set;
|
||||
sigemptyset( &block_set );
|
||||
|
||||
int sd = OpenSocket( monitor->Id() );
|
||||
|
||||
FrameHeader frame_header = { 0, 0, false, 0 };
|
||||
//unsigned char *image_data = 0;
|
||||
|
||||
fd_set rfds;
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
while( 1 )
|
||||
{
|
||||
struct timeval temp_timeout = timeout;
|
||||
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sd, &rfds);
|
||||
int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout );
|
||||
if( n_found == 0 )
|
||||
{
|
||||
Debug( 1, "Select timed out" );
|
||||
continue;
|
||||
}
|
||||
else if ( n_found < 0)
|
||||
{
|
||||
Error( "Select error: %s", strerror(errno) );
|
||||
ReopenSocket( sd, monitor->Id() );
|
||||
continue;
|
||||
}
|
||||
|
||||
sigprocmask( SIG_BLOCK, &block_set, 0 );
|
||||
|
||||
int n_bytes = read( sd, &frame_header, sizeof(frame_header) );
|
||||
if ( n_bytes != sizeof(frame_header) )
|
||||
{
|
||||
if ( n_bytes < 0 )
|
||||
{
|
||||
Error( "Can't read frame header: %s", strerror(errno) );
|
||||
}
|
||||
else if ( n_bytes > 0 )
|
||||
{
|
||||
Error( "Incomplete read of frame header, %d bytes only", n_bytes );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Socket closed at remote end" );
|
||||
}
|
||||
ReopenSocket( sd, monitor->Id() );
|
||||
continue;
|
||||
}
|
||||
Debug( 1, "Read frame header, expecting %ld bytes of image", frame_header.image_length );
|
||||
static unsigned char image_data[ZM_MAX_IMAGE_SIZE];
|
||||
|
||||
// Read for pipe and loop until bytes expected have been read or an error occurs
|
||||
int bytes_read = 0;
|
||||
do
|
||||
{
|
||||
n_bytes = read( sd, image_data+bytes_read, frame_header.image_length-bytes_read );
|
||||
if (n_bytes < 0) break; // break on error
|
||||
if (n_bytes < (int)frame_header.image_length)
|
||||
{
|
||||
// print some informational messages
|
||||
if (bytes_read == 0)
|
||||
>>>>>>> master
|
||||
{
|
||||
Debug(4,"Image read : Short read %d bytes of %d expected bytes",n_bytes,frame_header.image_length);
|
||||
}
|
||||
else if (bytes_read+n_bytes == (int)frame_header.image_length)
|
||||
{
|
||||
Debug(5,"Image read : Read rest of short read: %d bytes read total of %d bytes",n_bytes,frame_header.image_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug(6,"Image read : continuing, read %d bytes (%d so far)", n_bytes, bytes_read+n_bytes);
|
||||
}
|
||||
}
|
||||
bytes_read+= n_bytes;
|
||||
} while (n_bytes>0 && (bytes_read < (ssize_t)frame_header.image_length) );
|
||||
|
||||
// Print errors if there was a problem
|
||||
if ( n_bytes < 1 )
|
||||
{
|
||||
Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length);
|
||||
if ( n_bytes < 0 )
|
||||
{
|
||||
|
|
|
@ -303,6 +303,9 @@ int main( int argc, const char *argv[] )
|
|||
}
|
||||
else if ( source == ZMS_EVENT )
|
||||
{
|
||||
if ( ! event_id ) {
|
||||
Fatal( "Can't view an event without specifying an event_id." );
|
||||
}
|
||||
EventStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
|
|
|
@ -493,7 +493,6 @@ int main( int argc, char *argv[] )
|
|||
}
|
||||
if ( ! monitor->connect() ) {
|
||||
Error( "Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name() );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
char separator = ' ';
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
; This file is for unifying the coding style for different editors and IDEs.
|
||||
; More information at http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
|
@ -0,0 +1,22 @@
|
|||
# User specific & automatically generated files #
|
||||
#################################################
|
||||
/app/Config/database.php
|
||||
/app/tmp
|
||||
/lib/Cake/Console/Templates/skel/tmp/
|
||||
/plugins
|
||||
/vendors
|
||||
/build
|
||||
/dist
|
||||
/tags
|
||||
/app/webroot/events
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
|
@ -0,0 +1,5 @@
|
|||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteRule ^$ app/webroot/ [L]
|
||||
RewriteRule (.*) app/webroot/$1 [L]
|
||||
</IfModule>
|
|
@ -0,0 +1,116 @@
|
|||
language: php
|
||||
|
||||
php:
|
||||
- 5.2
|
||||
- 5.3
|
||||
- 5.4
|
||||
|
||||
env:
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
- DB=sqlite
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.4
|
||||
env:
|
||||
- PHPCS=1
|
||||
|
||||
before_script:
|
||||
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi"
|
||||
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test2;'; fi"
|
||||
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test3;'; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test2;' -U postgres -d cakephp_test; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi"
|
||||
- chmod -R 777 ./app/tmp
|
||||
- sudo apt-get install lighttpd
|
||||
- pear channel-discover pear.cakephp.org
|
||||
- pear install --alldeps cakephp/CakePHP_CodeSniffer
|
||||
- phpenv rehash
|
||||
- set +H
|
||||
- echo "<?php
|
||||
class DATABASE_CONFIG {
|
||||
private \$identities = array(
|
||||
'mysql' => array(
|
||||
'datasource' => 'Database/Mysql',
|
||||
'host' => '0.0.0.0',
|
||||
'login' => 'travis'
|
||||
),
|
||||
'pgsql' => array(
|
||||
'datasource' => 'Database/Postgres',
|
||||
'host' => '127.0.0.1',
|
||||
'login' => 'postgres',
|
||||
'database' => 'cakephp_test',
|
||||
'schema' => array(
|
||||
'default' => 'public',
|
||||
'test' => 'public',
|
||||
'test2' => 'test2',
|
||||
'test_database_three' => 'test3'
|
||||
)
|
||||
),
|
||||
'sqlite' => array(
|
||||
'datasource' => 'Database/Sqlite',
|
||||
'database' => array(
|
||||
'default' => ':memory:',
|
||||
'test' => ':memory:',
|
||||
'test2' => '/tmp/cakephp_test2.db',
|
||||
'test_database_three' => '/tmp/cakephp_test3.db'
|
||||
),
|
||||
)
|
||||
);
|
||||
public \$default = array(
|
||||
'persistent' => false,
|
||||
'host' => '',
|
||||
'login' => '',
|
||||
'password' => '',
|
||||
'database' => 'cakephp_test',
|
||||
'prefix' => ''
|
||||
);
|
||||
public \$test = array(
|
||||
'persistent' => false,
|
||||
'host' => '',
|
||||
'login' => '',
|
||||
'password' => '',
|
||||
'database' => 'cakephp_test',
|
||||
'prefix' => ''
|
||||
);
|
||||
public \$test2 = array(
|
||||
'persistent' => false,
|
||||
'host' => '',
|
||||
'login' => '',
|
||||
'password' => '',
|
||||
'database' => 'cakephp_test2',
|
||||
'prefix' => ''
|
||||
);
|
||||
public \$test_database_three = array(
|
||||
'persistent' => false,
|
||||
'host' => '',
|
||||
'login' => '',
|
||||
'password' => '',
|
||||
'database' => 'cakephp_test3',
|
||||
'prefix' => ''
|
||||
);
|
||||
public function __construct() {
|
||||
\$db = 'mysql';
|
||||
if (!empty(\$_SERVER['DB'])) {
|
||||
\$db = \$_SERVER['DB'];
|
||||
}
|
||||
foreach (array('default', 'test', 'test2', 'test_database_three') as \$source) {
|
||||
\$config = array_merge(\$this->{\$source}, \$this->identities[\$db]);
|
||||
if (is_array(\$config['database'])) {
|
||||
\$config['database'] = \$config['database'][\$source];
|
||||
}
|
||||
if (!empty(\$config['schema']) && is_array(\$config['schema'])) {
|
||||
\$config['schema'] = \$config['schema'][\$source];
|
||||
}
|
||||
\$this->{\$source} = \$config;
|
||||
}
|
||||
}
|
||||
}" > app/Config/database.php
|
||||
|
||||
script:
|
||||
- sh -c "if [ '$PHPCS' != '1' ]; then ./lib/Cake/Console/cake test core AllTests --stderr; else phpcs -p --extensions=php --standard=CakePHP ./lib/Cake; fi"
|
||||
|
||||
notifications:
|
||||
email: false
|
|
@ -117,6 +117,7 @@ $statusData = array(
|
|||
"Height" => true,
|
||||
"Length" => true,
|
||||
"Frames" => true,
|
||||
"DefaultVideo" => true,
|
||||
"AlarmFrames" => true,
|
||||
"TotScore" => true,
|
||||
"AvgScore" => true,
|
||||
|
@ -385,31 +386,33 @@ function getNearEvents()
|
|||
else
|
||||
$midSql = '';
|
||||
|
||||
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc');
|
||||
$sql = "select E.* as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc');
|
||||
$result = dbQuery( $sql );
|
||||
while ( $id = dbFetchNext( $result, 'Id' ) )
|
||||
{
|
||||
if ( $id == $eventId )
|
||||
{
|
||||
$prevId = dbFetchNext( $result, 'Id' );
|
||||
$prevEvent = dbFetchNext( $result );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder";
|
||||
$sql = "select E.* as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder";
|
||||
$result = dbQuery( $sql );
|
||||
while ( $id = dbFetchNext( $result, 'Id' ) )
|
||||
{
|
||||
if ( $id == $eventId )
|
||||
{
|
||||
$nextId = dbFetchNext( $result, 'Id' );
|
||||
$nextEvent = dbFetchNext( $result );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result = array( 'EventId'=>$eventId );
|
||||
$result['PrevEventId'] = empty($prevId)?0:$prevId;
|
||||
$result['NextEventId'] = empty($nextId)?0:$nextId;
|
||||
$result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id'];
|
||||
$result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id'];
|
||||
$result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent));
|
||||
$result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent));
|
||||
return( $result );
|
||||
}
|
||||
|
||||
|
|
|
@ -134,8 +134,10 @@ function loadConfigFile() {
|
|||
continue;
|
||||
elseif ( preg_match( '/^\s*#/', $str ))
|
||||
continue;
|
||||
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches ))
|
||||
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) {
|
||||
Configure::write("$matches[1]", "$matches[2]");
|
||||
define( $matches[1], $matches[2] );
|
||||
}
|
||||
}
|
||||
fclose( $cfg );
|
||||
}
|
||||
|
|
|
@ -67,10 +67,10 @@ class DATABASE_CONFIG {
|
|||
public $default = array(
|
||||
'datasource' => 'Database/Mysql',
|
||||
'persistent' => false,
|
||||
'host' => '@ZM_DB_HOST@',
|
||||
'login' => '@ZM_DB_USER@',
|
||||
'password' => '@ZM_DB_PASS@',
|
||||
'database' => '@ZM_DB_NAME@',
|
||||
'host' => ZM_DB_HOST,
|
||||
'login' => ZM_DB_USER,
|
||||
'password' => ZM_DB_PASS,
|
||||
'database' => ZM_DB_NAME,
|
||||
'prefix' => '',
|
||||
//'encoding' => 'utf8',
|
||||
);
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
/**
|
||||
* Servers Controller
|
||||
*
|
||||
* @property Server $Server
|
||||
* @property PaginatorComponent $Paginator
|
||||
*/
|
||||
class ServersController extends AppController {
|
||||
|
||||
|
||||
/**
|
||||
* Components
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $components = array('Paginator', 'RequestHandler');
|
||||
|
||||
|
||||
public function beforeFilter() {
|
||||
parent::beforeFilter();
|
||||
$canView = $this->Session->Read('systemPermission');
|
||||
if ($canView =='None') {
|
||||
throw new UnauthorizedException(__('Insufficient Privileges'));
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* index method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index() {
|
||||
$this->Server->recursive = 0;
|
||||
|
||||
$options='';
|
||||
$servers = $this->Server->find('all',$options);
|
||||
$this->set(array(
|
||||
'servers' => $servers,
|
||||
'_serialize' => array('servers')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* view method
|
||||
*
|
||||
* @throws NotFoundException
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
public function view($id = null) {
|
||||
$this->Server->recursive = 0;
|
||||
if (!$this->Server->exists($id)) {
|
||||
throw new NotFoundException(__('Invalid server'));
|
||||
}
|
||||
$restricted = '';
|
||||
|
||||
$options = array('conditions' => array(
|
||||
array('Server.' . $this->Server->primaryKey => $id),
|
||||
$restricted
|
||||
)
|
||||
);
|
||||
$server = $this->Server->find('first', $options);
|
||||
$this->set(array(
|
||||
'server' => $server,
|
||||
'_serialize' => array('server')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* add method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add() {
|
||||
if ($this->request->is('post')) {
|
||||
|
||||
if ($this->Session->Read('systemPermission') != 'Edit')
|
||||
{
|
||||
throw new UnauthorizedException(__('Insufficient privileges'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->Server->create();
|
||||
if ($this->Server->save($this->request->data)) {
|
||||
# Might be nice to send it a start request
|
||||
#$this->daemonControl($this->Server->id, 'start', $this->request->data);
|
||||
return $this->flash(__('The server has been saved.'), array('action' => 'index'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* edit method
|
||||
*
|
||||
* @throws NotFoundException
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
public function edit($id = null) {
|
||||
$this->Server->id = $id;
|
||||
|
||||
if (!$this->Server->exists($id)) {
|
||||
throw new NotFoundException(__('Invalid server'));
|
||||
}
|
||||
if ($this->Session->Read('systemPermission') != 'Edit')
|
||||
{
|
||||
throw new UnauthorizedException(__('Insufficient privileges'));
|
||||
return;
|
||||
}
|
||||
if ($this->Server->save($this->request->data)) {
|
||||
$message = 'Saved';
|
||||
} else {
|
||||
$message = 'Error';
|
||||
}
|
||||
|
||||
$this->set(array(
|
||||
'message' => $message,
|
||||
'_serialize' => array('message')
|
||||
));
|
||||
// - restart this server after change
|
||||
#$this->daemonControl($this->Server->id, 'restart', $this->request->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete method
|
||||
*
|
||||
* @throws NotFoundException
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
public function delete($id = null) {
|
||||
$this->Server->id = $id;
|
||||
if (!$this->Server->exists()) {
|
||||
throw new NotFoundException(__('Invalid server'));
|
||||
}
|
||||
if ($this->Session->Read('systemPermission') != 'Edit')
|
||||
{
|
||||
throw new UnauthorizedException(__('Insufficient privileges'));
|
||||
return;
|
||||
}
|
||||
$this->request->allowMethod('post', 'delete');
|
||||
|
||||
#$this->daemonControl($this->Server->id, 'stop');
|
||||
|
||||
if ($this->Server->delete()) {
|
||||
return $this->flash(__('The server has been deleted.'), array('action' => 'index'));
|
||||
} else {
|
||||
return $this->flash(__('The server could not be deleted. Please, try again.'), array('action' => 'index'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
/**
|
||||
* Server Model
|
||||
*
|
||||
* @property Event $Event
|
||||
* @property Zone $Zone
|
||||
*/
|
||||
class Server extends AppModel {
|
||||
|
||||
/**
|
||||
* Use table
|
||||
*
|
||||
* @var mixed False or table name
|
||||
*/
|
||||
public $useTable = 'Servers';
|
||||
|
||||
/**
|
||||
* Primary key field
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $primaryKey = 'Id';
|
||||
|
||||
/**
|
||||
* Display field
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $displayField = 'Name';
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $validate = array(
|
||||
'Id' => array(
|
||||
'numeric' => array(
|
||||
'rule' => array('numeric'),
|
||||
//'message' => 'Your custom message here',
|
||||
//'allowEmpty' => false,
|
||||
//'required' => false,
|
||||
//'last' => false, // Stop validation after this rule
|
||||
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
//The Associations below have been created with all possible keys, those that are not needed can be removed
|
||||
|
||||
/**
|
||||
* hasMany associations
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $hasMany = array(
|
||||
'Monitor' => array(
|
||||
'className' => 'Monitor',
|
||||
'foreignKey' => 'ServerId',
|
||||
'dependent' => false,
|
||||
'conditions' => '',
|
||||
'fields' => '',
|
||||
'order' => '',
|
||||
'limit' => '',
|
||||
'offset' => '',
|
||||
'exclusive' => '',
|
||||
'finderQuery' => '',
|
||||
'counterQuery' => ''
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
echo json_encode($message);
|
||||
echo json_encode($server);
|
|
@ -0,0 +1 @@
|
|||
echo json_encode($servers);
|
|
@ -0,0 +1 @@
|
|||
echo json_encode($server);
|
|
@ -0,0 +1,2 @@
|
|||
$xml = Xml::fromArray(array('response' => $message));
|
||||
echo $xml->asXML();
|
|
@ -0,0 +1,2 @@
|
|||
$xml = Xml::fromArray(array('response' => $monitors));
|
||||
echo $xml->asXML();
|
|
@ -0,0 +1,2 @@
|
|||
$xml = Xml::fromArray(array('response' => $monitor));
|
||||
echo $xml->asXML();
|
|
@ -0,0 +1,9 @@
|
|||
AUTOMAKE_OPTIONS = gnu
|
||||
|
||||
webdir = @WEB_PREFIX@/css
|
||||
|
||||
dist_web_DATA = \
|
||||
bootstrap.min.css \
|
||||
reset.css \
|
||||
spinner.css \
|
||||
overlay.css
|
File diff suppressed because one or more lines are too long
|
@ -33,11 +33,18 @@ class Event {
|
|||
public function __call( $fn, array $args){
|
||||
if(isset($this->{$fn})){
|
||||
return $this->{$fn};
|
||||
#array_unshift($args, $this);
|
||||
#call_user_func_array( $this->{$fn}, $args);
|
||||
#array_unshift($args, $this);
|
||||
#call_user_func_array( $this->{$fn}, $args);
|
||||
}
|
||||
}
|
||||
|
||||
public function Time() {
|
||||
if ( ! isset( $this->{'Time'} ) ) {
|
||||
$this->{'Time'} = strtotime($this->{'StartTime'});
|
||||
}
|
||||
return $this->{'Time'};
|
||||
}
|
||||
|
||||
public function Path() {
|
||||
$Storage = $this->Storage();
|
||||
return $Storage->Path().'/'.$this->Relative_Path();
|
||||
|
@ -50,7 +57,7 @@ class Event {
|
|||
$event_path =
|
||||
$this->{'MonitorId'}
|
||||
.'/'.strftime( "%y/%m/%d/%H/%M/%S",
|
||||
$this->{'Time'}
|
||||
$this->Time()
|
||||
)
|
||||
;
|
||||
}
|
||||
|
@ -66,7 +73,80 @@ class Event {
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
public function LinkPath() {
|
||||
if ( ZM_USE_DEEP_STORAGE ) {
|
||||
return $this->{'MonitorId'} .'/'.strftime( "%y/%m/%d/.", $this->Time()).$this->{'Id'};
|
||||
}
|
||||
Error("Calling Link_Path when not using deep storage");
|
||||
return '';
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) );
|
||||
if ( !ZM_OPT_FAST_DELETE ) {
|
||||
dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) );
|
||||
dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) );
|
||||
if ( ZM_USE_DEEP_STORAGE ) {
|
||||
|
||||
# Assumption: All events haev a start time
|
||||
$start_date = date_parse( $this->{'StartTime'} );
|
||||
$start_date['year'] = $start_date['year'] % 100;
|
||||
|
||||
$Storage = $this->Storage();
|
||||
# So this is because ZM creates a link under teh day pointing to the time that the event happened.
|
||||
$eventlink_path = $Storage->Path().'/'.$this->Link_Path();
|
||||
|
||||
if ( $id_files = glob( $eventlink_path ) ) {
|
||||
# I know we are using arrays here, but really there can only ever be 1 in the array
|
||||
$eventPath = preg_replace( '/\.'.$event['Id'].'$/', readlink($id_files[0]), $id_files[0] );
|
||||
deletePath( $eventPath );
|
||||
deletePath( $id_files[0] );
|
||||
$pathParts = explode( '/', $eventPath );
|
||||
for ( $i = count($pathParts)-1; $i >= 2; $i-- ) {
|
||||
$deletePath = join( '/', array_slice( $pathParts, 0, $i ) );
|
||||
if ( !glob( $deletePath."/*" ) ) {
|
||||
deletePath( $deletePath );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warning( "Found no event files under $eventlink_path" );
|
||||
} # end if found files
|
||||
} else {
|
||||
$eventPath = $this->Path();
|
||||
deletePath( $eventPath );
|
||||
} # USE_DEEP_STORAGE OR NOT
|
||||
} # ! ZM_OPT_FAST_DELETE
|
||||
} # end Event->delete
|
||||
|
||||
public function getStreamSrc( $args, $querySep='&' ) {
|
||||
return ZM_BASE_URL.'/index.php?view=view_video&eid='.$this->{'Id'};
|
||||
|
||||
$streamSrc = ZM_BASE_URL.ZM_PATH_ZMS;
|
||||
|
||||
$args[] = "source=event&event=".$this->{'Id'};
|
||||
|
||||
if ( ZM_OPT_USE_AUTH ) {
|
||||
if ( ZM_AUTH_RELAY == "hashed" ) {
|
||||
$args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS );
|
||||
} elseif ( ZM_AUTH_RELAY == "plain" ) {
|
||||
$args[] = "user=".$_SESSION['username'];
|
||||
$args[] = "pass=".$_SESSION['password'];
|
||||
} elseif ( ZM_AUTH_RELAY == "none" ) {
|
||||
$args[] = "user=".$_SESSION['username'];
|
||||
}
|
||||
}
|
||||
if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) {
|
||||
$args[] = "connkey=".$GLOBALS['connkey'];
|
||||
}
|
||||
if ( ZM_RAND_STREAM ) {
|
||||
$args[] = "rand=".time();
|
||||
}
|
||||
|
||||
if ( count($args) ) {
|
||||
$streamSrc .= "?".join( $querySep, $args );
|
||||
}
|
||||
|
||||
return( $streamSrc );
|
||||
} // end function getStreamSrc
|
||||
} # end class
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
require_once( 'database.php' );
|
||||
require_once( 'Event.php' );
|
||||
|
||||
class Frame {
|
||||
public function __construct( $IdOrRow ) {
|
||||
$row = NULL;
|
||||
if ( $IdOrRow ) {
|
||||
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
|
||||
$row = dbFetchOne( 'SELECT * FROM Frames WHERE Id=?', NULL, array( $IdOrRow ) );
|
||||
if ( ! $row ) {
|
||||
Error("Unable to load Frame record for Id=" . $IdOrRow );
|
||||
}
|
||||
} elseif ( is_array( $IdOrRow ) ) {
|
||||
$row = $IdOrRow;
|
||||
} else {
|
||||
Error("Unknown argument passed to Frame Constructor ($IdOrRow)");
|
||||
return;
|
||||
}
|
||||
} # end if isset($IdOrRow)
|
||||
|
||||
if ( $row ) {
|
||||
foreach ($row as $k => $v) {
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
} else {
|
||||
Error("No row for Frame " . $IdOrRow );
|
||||
}
|
||||
} // end function __construct
|
||||
public function Storage() {
|
||||
return $this->Event()->Storage();
|
||||
}
|
||||
public function Event() {
|
||||
return new Event( $this->{'EventId'} );
|
||||
}
|
||||
public function __call( $fn, array $args){
|
||||
if(isset($this->{$fn})){
|
||||
return $this->{$fn};
|
||||
#array_unshift($args, $this);
|
||||
#call_user_func_array( $this->{$fn}, $args);
|
||||
}
|
||||
}
|
||||
|
||||
public function Path() {
|
||||
$Storage = $this->Storage();
|
||||
return $Storage->Path().'/'.$this->Relative_Path();
|
||||
}
|
||||
public function Relative_Path() {
|
||||
$event_path = "";
|
||||
|
||||
if ( ZM_USE_DEEP_STORAGE )
|
||||
{
|
||||
$event_path =
|
||||
$this->{'MonitorId'}
|
||||
.'/'.strftime( "%y/%m/%d/%H/%M/%S",
|
||||
$this->Time()
|
||||
)
|
||||
;
|
||||
}
|
||||
else
|
||||
{
|
||||
$event_path =
|
||||
$this->{'MonitorId'}
|
||||
.'/'.$this->{'Id'}
|
||||
;
|
||||
}
|
||||
|
||||
return( $event_path );
|
||||
|
||||
}
|
||||
|
||||
public function getImageSrc( ) {
|
||||
return ZM_BASE_URL.'/index.php?view=image&fid='.$this->{'Id'};
|
||||
} // end function getImageSrc
|
||||
} # end class
|
||||
?>
|
|
@ -28,10 +28,17 @@ class Monitor {
|
|||
foreach ($s as $k => $v) {
|
||||
if ( $k == 'Id' ) {
|
||||
continue;
|
||||
}
|
||||
} else if ( $k == 'Protocol' ) {
|
||||
$this->{'ControlProtocol'} = $v;
|
||||
} else if ( $k == 'Name' ) {
|
||||
$this->{'ControlName'} = $v;
|
||||
} else if ( $k == 'Type' ) {
|
||||
$this->{'ControlType'} = $v;
|
||||
} else {
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Error("No row for Monitor " . $IdOrRow );
|
||||
|
@ -79,7 +86,7 @@ class Monitor {
|
|||
}
|
||||
|
||||
return( $streamSrc );
|
||||
} // end function etStreamSrc
|
||||
} // end function getStreamSrc
|
||||
public function Width() {
|
||||
if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) {
|
||||
return $this->{'Height'};
|
||||
|
@ -92,5 +99,18 @@ class Monitor {
|
|||
}
|
||||
return $this->{'Height'};
|
||||
}
|
||||
public function set( $data ) {
|
||||
foreach ($data as $k => $v) {
|
||||
if ( is_array( $v ) ) {
|
||||
# perhaps should turn into a comma-separated string
|
||||
$this->{$k} = implode(',',$v);
|
||||
} else if ( is_string( $v ) ) {
|
||||
$this->{$k} = trim( $v );
|
||||
} else {
|
||||
Error( "Unknown type of var " . gettype( $v ) );
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -26,6 +26,8 @@ class Storage {
|
|||
public function Path() {
|
||||
if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) {
|
||||
return $this->{'Path'};
|
||||
} else if ( ! isset($this->{'Id'}) ) {
|
||||
return ZM_DIR_EVENTS;
|
||||
}
|
||||
return $this->{'Name'};
|
||||
}
|
||||
|
|
|
@ -527,6 +527,7 @@ if ( !empty($action) )
|
|||
'DoNativeMotDet' => 'toggle',
|
||||
'Exif' => 'toggle',
|
||||
'RTSPDescribe' => 'toggle',
|
||||
'RecordAudio' => 'toggle',
|
||||
);
|
||||
|
||||
$columns = getTableColumns( 'Monitors' );
|
||||
|
|
|
@ -19,24 +19,20 @@
|
|||
//
|
||||
|
||||
// Compatibility functions
|
||||
if ( version_compare( phpversion(), "4.3.0", "<") )
|
||||
{
|
||||
function ob_get_clean()
|
||||
{
|
||||
if ( version_compare( phpversion(), "4.3.0", "<") ) {
|
||||
function ob_get_clean() {
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return( $buffer );
|
||||
}
|
||||
}
|
||||
|
||||
function userLogin( $username, $password="", $passwordHashed=false )
|
||||
{
|
||||
function userLogin( $username, $password="", $passwordHashed=false ) {
|
||||
global $user, $cookies;
|
||||
|
||||
$sql = "select * from Users where Enabled = 1";
|
||||
$sql_values = NULL;
|
||||
if ( ZM_AUTH_TYPE == "builtin" )
|
||||
{
|
||||
if ( ZM_AUTH_TYPE == "builtin" ) {
|
||||
if ( $passwordHashed ) {
|
||||
$sql .= " AND Username=? AND Password=?";
|
||||
} else {
|
||||
|
@ -48,23 +44,18 @@ function userLogin( $username, $password="", $passwordHashed=false )
|
|||
$sql_values = array( $username );
|
||||
}
|
||||
$_SESSION['username'] = $username;
|
||||
if ( ZM_AUTH_RELAY == "plain" )
|
||||
{
|
||||
if ( ZM_AUTH_RELAY == "plain" ) {
|
||||
// Need to save this in session
|
||||
$_SESSION['password'] = $password;
|
||||
}
|
||||
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
|
||||
if ( $dbUser = dbFetchOne( $sql, NULL, $sql_values ) )
|
||||
{
|
||||
if ( $dbUser = dbFetchOne( $sql, NULL, $sql_values ) ) {
|
||||
Info( "Login successful for user \"$username\"" );
|
||||
$_SESSION['user'] = $user = $dbUser;
|
||||
if ( ZM_AUTH_TYPE == "builtin" )
|
||||
{
|
||||
if ( ZM_AUTH_TYPE == "builtin" ) {
|
||||
$_SESSION['passwordHash'] = $user['Password'];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Warning( "Login denied for user \"$username\"" );
|
||||
unset( $user );
|
||||
}
|
||||
|
@ -72,8 +63,7 @@ function userLogin( $username, $password="", $passwordHashed=false )
|
|||
session_write_close();
|
||||
}
|
||||
|
||||
function userLogout()
|
||||
{
|
||||
function userLogout() {
|
||||
global $user;
|
||||
$username = $user['Username'];
|
||||
|
||||
|
@ -85,8 +75,7 @@ function userLogout()
|
|||
session_destroy();
|
||||
}
|
||||
|
||||
function noCacheHeaders()
|
||||
{
|
||||
function noCacheHeaders() {
|
||||
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
||||
header("Last-Modified: ".gmdate( "D, d M Y H:i:s" )." GMT"); // always modified
|
||||
header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1
|
||||
|
@ -97,11 +86,11 @@ function noCacheHeaders()
|
|||
function CORSHeaders() {
|
||||
if ( isset( $_SERVER['HTTP_ORIGIN'] ) ) {
|
||||
|
||||
# The following is left for future reference/use.
|
||||
# The following is left for future reference/use.
|
||||
$valid = false;
|
||||
$servers = dbFetchAll( 'SELECT * FROM Servers' );
|
||||
if ( sizeof($servers) <= 1 ) {
|
||||
# Only need CORSHeaders in the event that there are multiple servers in use.
|
||||
# Only need CORSHeaders in the event that there are multiple servers in use.
|
||||
return;
|
||||
}
|
||||
foreach( dbFetchAll( 'SELECT * FROM Servers' ) as $row ) {
|
||||
|
@ -118,8 +107,7 @@ function CORSHeaders() {
|
|||
}
|
||||
}
|
||||
|
||||
function getAuthUser( $auth )
|
||||
{
|
||||
function getAuthUser( $auth ) {
|
||||
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" && !empty($auth) )
|
||||
{
|
||||
$remoteAddr = "";
|
||||
|
@ -229,14 +217,20 @@ function getMimeType( $file )
|
|||
return( trim( exec( 'file -bi '.escapeshellarg( $file ).' 2>/dev/null' ) ) );
|
||||
}
|
||||
|
||||
function outputVideoStream( $id, $src, $width, $height, $format, $title="" )
|
||||
{
|
||||
if ( file_exists( $src ) )
|
||||
function outputVideoStream( $id, $src, $width, $height, $format, $title="" ) {
|
||||
echo getVideoStreamHTML( $id, $src, $width, $height, $format, $title );
|
||||
}
|
||||
|
||||
function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) {
|
||||
$html = '';
|
||||
$width = validInt($width);
|
||||
$height = validInt($height);
|
||||
$title = validHtmlStr($title);
|
||||
|
||||
if ( file_exists( $src ) ) {
|
||||
$mimeType = getMimeType( $src );
|
||||
else
|
||||
{
|
||||
switch( $format )
|
||||
{
|
||||
} else {
|
||||
switch( $format ) {
|
||||
case 'asf' :
|
||||
$mimeType = "video/x-ms-asf";
|
||||
break;
|
||||
|
@ -264,207 +258,184 @@ function outputVideoStream( $id, $src, $width, $height, $format, $title="" )
|
|||
}
|
||||
if ( !$mimeType || ($mimeType == 'application/octet-stream') )
|
||||
$mimeType = 'video/'.$format;
|
||||
$objectTag = false;
|
||||
if ( ZM_WEB_USE_OBJECT_TAGS )
|
||||
{
|
||||
switch( $mimeType )
|
||||
{
|
||||
if ( ZM_WEB_USE_OBJECT_TAGS ) {
|
||||
switch( $mimeType ) {
|
||||
case "video/x-ms-asf" :
|
||||
case "video/x-msvideo" :
|
||||
case "video/mp4" :
|
||||
{
|
||||
if ( isWindows() )
|
||||
{
|
||||
?>
|
||||
<object id="<?php echo $id ?>" width="<?php echo validNum($width) ?>" height="<?php echo validNum($height) ?>"
|
||||
classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
|
||||
codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,0,02,902"
|
||||
standby="Loading Microsoft Windows Media Player components..."
|
||||
type="<?php echo $mimeType ?>">
|
||||
<param name="FileName" value="<?php echo $src ?>"/>
|
||||
<param name="autoStart" value="1"/>
|
||||
<param name="showControls" value="0"/>
|
||||
<embed type="<?php echo $mimeType ?>"
|
||||
pluginspage="http://www.microsoft.com/Windows/MediaPlayer/"
|
||||
src="<?php echo $src ?>"
|
||||
name="<?php echo validHtmlStr($title) ?>"
|
||||
width="<?php echo validNum($width) ?>"
|
||||
height="<?php echo validInt($height) ?>"
|
||||
autostart="1"
|
||||
showcontrols="0">
|
||||
</embed>
|
||||
</object>
|
||||
<?php
|
||||
$objectTag = true;
|
||||
if ( isWindows() ) {
|
||||
return '<object id="'.$id.'" width="'.$width.'" height="'.$height.'
|
||||
classid="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"
|
||||
codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=6,0,02,902"
|
||||
standby="Loading Microsoft Windows Media Player components..."
|
||||
type="'.$mimeType.'">
|
||||
<param name="FileName" value="'.$src.'"/>
|
||||
<param name="autoStart" value="1"/>
|
||||
<param name="showControls" value="0"/>
|
||||
<embed type="'.$mimeType.'"
|
||||
pluginspage="http://www.microsoft.com/Windows/MediaPlayer/"
|
||||
src="'.$src.'"
|
||||
name="'.$title.'"
|
||||
width="'.$width.'"
|
||||
height="'.$height.'"
|
||||
autostart="1"
|
||||
showcontrols="0">
|
||||
</embed>
|
||||
</object>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "video/quicktime" :
|
||||
{
|
||||
?>
|
||||
<object id="<?php echo $id ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"
|
||||
classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
|
||||
codebase="http://www.apple.com/qtactivex/qtplugin.cab"
|
||||
type="<?php echo $mimeType ?>">
|
||||
<param name="src" value="<?php echo $src ?>"/>
|
||||
<param name="autoplay" VALUE="true"/>
|
||||
<param name="controller" VALUE="false"/>
|
||||
<embed type="<?php echo $mimeType ?>"
|
||||
src="<?php echo $src ?>"
|
||||
pluginspage="http://www.apple.com/quicktime/download/"
|
||||
name="<?php echo validHtmlStr($title) ?>"
|
||||
width="<?php echo validInt($width) ?>"
|
||||
height="<?php echo validInt($height) ?>"
|
||||
autoplay="true"
|
||||
controller="true">
|
||||
</embed>
|
||||
</object>
|
||||
<?php
|
||||
$objectTag = true;
|
||||
break;
|
||||
return '<object id="'.$id.'" width="'.$width.'" height="'.$height.'"
|
||||
classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
|
||||
codebase="http://www.apple.com/qtactivex/qtplugin.cab"
|
||||
type="'.$mimeType.'">
|
||||
<param name="src" value="'.$src.'"/>
|
||||
<param name="autoplay" VALUE="true"/>
|
||||
<param name="controller" VALUE="false"/>
|
||||
<embed type="'.$mimeType.'"
|
||||
src="'.$src.'"
|
||||
pluginspage="http://www.apple.com/quicktime/download/"
|
||||
name="'.$title.'" width="'.$width.'" height="'.$height.'"
|
||||
autoplay="true"
|
||||
controller="true">
|
||||
</embed>
|
||||
</object>';
|
||||
}
|
||||
case "application/x-shockwave-flash" :
|
||||
{
|
||||
?>
|
||||
<object id="<?php echo $id ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"
|
||||
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
|
||||
codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"
|
||||
type="<?php echo $mimeType ?>">
|
||||
<param name="movie" value="<?php echo $src ?>"/>
|
||||
<param name="quality" value="high"/>
|
||||
<param name="bgcolor" value="#ffffff"/>
|
||||
<embed type="<?php echo $mimeType ?>"
|
||||
pluginspage="http://www.macromedia.com/go/getflashplayer"
|
||||
src="<?php echo $src ?>"
|
||||
name="<?php echo validHtmlStr($title) ?>"
|
||||
width="<?php echo validInt($width) ?>"
|
||||
height="<?php echo validInt($height) ?>"
|
||||
quality="high"
|
||||
bgcolor="#ffffff">
|
||||
</embed>
|
||||
</object>
|
||||
<?php
|
||||
$objectTag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !$objectTag )
|
||||
{
|
||||
?>
|
||||
<embed<?php echo isset($mimeType)?(' type="'.$mimeType.'"'):"" ?>
|
||||
src="<?php echo $src ?>"
|
||||
name="<?php echo validHtmlStr($title) ?>"
|
||||
width="<?php echo validInt($width) ?>"
|
||||
height="<?php echo validInt($height) ?>"
|
||||
autostart="1"
|
||||
autoplay="1"
|
||||
showcontrols="0"
|
||||
controller="0">
|
||||
</embed>
|
||||
<?php
|
||||
return '<object id="'.$id.'" width="'.$width.'" height="'.$height.'"
|
||||
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
|
||||
codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"
|
||||
type="'.$mimeType.'">
|
||||
<param name="movie" value="'.$src.'"/>
|
||||
<param name="quality" value="high"/>
|
||||
<param name="bgcolor" value="#ffffff"/>
|
||||
<embed type="'.$mimeType.'"
|
||||
pluginspage="http://www.macromedia.com/go/getflashplayer"
|
||||
src="'.$src.'"
|
||||
name="'.$title.'"
|
||||
width="'.$width.'"
|
||||
height="'.$height.'"
|
||||
quality="high"
|
||||
bgcolor="#ffffff">
|
||||
</embed>
|
||||
</object>';
|
||||
}
|
||||
} # end switch
|
||||
} # end if use object tags
|
||||
return '<embed'. ( isset($mimeType)?(' type="'.$mimeType.'"'):'' ). '
|
||||
src="'.$src.'"
|
||||
name="'.$title.'"
|
||||
width="'.$width.'"
|
||||
height="'.$height.'"
|
||||
autostart="1"
|
||||
autoplay="1"
|
||||
showcontrols="0"
|
||||
controller="0">
|
||||
</embed>';
|
||||
}
|
||||
|
||||
function outputImageStream( $id, $src, $width, $height, $title="" )
|
||||
{
|
||||
function outputImageStream( $id, $src, $width, $height, $title="" ) {
|
||||
echo getImageStream( $id, $src, $width, $height, $title );
|
||||
}
|
||||
|
||||
|
||||
function getImageStream( $id, $src, $width, $height, $title="" ) {
|
||||
if ( canStreamIframe() ) {
|
||||
?>
|
||||
<iframe id="<?php echo $id ?>" src="<?php echo $src ?>" alt="<?php echo validHtmlStr($title) ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"/>
|
||||
<?php
|
||||
return '<iframe id="'.$id.'" src="'.$src.'" alt="'. validHtmlStr($title) .'" width="'. validInt($width)." height=".validInt($height).'"/>';
|
||||
} else {
|
||||
?>
|
||||
<img id="<?php echo $id ?>" src="<?php echo $src ?>" alt="<?php echo validHtmlStr($title) ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"/>
|
||||
<?php
|
||||
return '<img id="'.$id.'" src="'.$src.'" alt="'. validHtmlStr($title) .'" width="'. validInt($width) .'" height="'. validInt( $height ).'"/>';
|
||||
}
|
||||
}
|
||||
|
||||
function outputControlStream( $src, $width, $height, $monitor, $scale, $target )
|
||||
{
|
||||
?>
|
||||
<form name="ctrlForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" target="<?php echo $target ?>">
|
||||
<input type="hidden" name="view" value="blank">
|
||||
<input type="hidden" name="mid" value="<?php echo $monitor['Id'] ?>">
|
||||
<input type="hidden" name="action" value="control">
|
||||
<?php
|
||||
?>
|
||||
<form name="ctrlForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" target="<?php echo $target ?>">
|
||||
<input type="hidden" name="view" value="blank">
|
||||
<input type="hidden" name="mid" value="<?php echo $monitor['Id'] ?>">
|
||||
<input type="hidden" name="action" value="control">
|
||||
<?php
|
||||
if ( $monitor['CanMoveMap'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveMap">
|
||||
<?php
|
||||
}
|
||||
elseif ( $monitor['CanMoveRel'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="movePseudoMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="movePseudoMap">
|
||||
<?php
|
||||
}
|
||||
elseif ( $monitor['CanMoveCon'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveConMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveConMap">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<input type="hidden" name="scale" value="<?php echo $scale ?>">
|
||||
<input type="image" src="<?php echo $src ?>" width="<?php echo $width ?>" height="<?php echo $height ?>">
|
||||
</form>
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="scale" value="<?php echo $scale ?>">
|
||||
<input type="image" src="<?php echo $src ?>" width="<?php echo $width ?>" height="<?php echo $height ?>">
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
|
||||
function outputHelperStream( $id, $src, $width, $height, $title="" )
|
||||
{
|
||||
?>
|
||||
<applet id="<?php echo $id ?>" code="com.charliemouse.cambozola.Viewer"
|
||||
archive="<?php echo ZM_PATH_CAMBOZOLA ?>"
|
||||
align="middle"
|
||||
width="<?php echo $width ?>"
|
||||
height="<?php echo $height ?>"
|
||||
title="<?php echo $title ?>">
|
||||
<param name="accessories" value="none"/>
|
||||
<param name="url" value="<?php echo $src ?>"/>
|
||||
</applet>
|
||||
<?php
|
||||
?>
|
||||
<applet id="<?php echo $id ?>" code="com.charliemouse.cambozola.Viewer"
|
||||
archive="<?php echo ZM_PATH_CAMBOZOLA ?>"
|
||||
align="middle"
|
||||
width="<?php echo $width ?>"
|
||||
height="<?php echo $height ?>"
|
||||
title="<?php echo $title ?>">
|
||||
<param name="accessories" value="none"/>
|
||||
<param name="url" value="<?php echo $src ?>"/>
|
||||
</applet>
|
||||
<?php
|
||||
}
|
||||
|
||||
function outputImageStill( $id, $src, $width, $height, $title="" )
|
||||
{
|
||||
?>
|
||||
<img id="<?php echo $id ?>" src="<?php echo $src ?>" alt="<?php echo $title ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"/>
|
||||
<?php
|
||||
?>
|
||||
<img id="<?php echo $id ?>" src="<?php echo $src ?>" alt="<?php echo $title ?>" width="<?php echo $width ?>" height="<?php echo $height ?>"/>
|
||||
<?php
|
||||
}
|
||||
|
||||
function outputControlStill( $src, $width, $height, $monitor, $scale, $target )
|
||||
{
|
||||
?>
|
||||
<form name="ctrlForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" target="<?php echo $target ?>">
|
||||
<input type="hidden" name="view" value="blank">
|
||||
<input type="hidden" name="mid" value="<?php echo $monitor['Id'] ?>">
|
||||
<input type="hidden" name="action" value="control">
|
||||
<?php
|
||||
?>
|
||||
<form name="ctrlForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" target="<?php echo $target ?>">
|
||||
<input type="hidden" name="view" value="blank">
|
||||
<input type="hidden" name="mid" value="<?php echo $monitor['Id'] ?>">
|
||||
<input type="hidden" name="action" value="control">
|
||||
<?php
|
||||
if ( $monitor['CanMoveMap'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveMap">
|
||||
<?php
|
||||
}
|
||||
elseif ( $monitor['CanMoveRel'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="movePseudoMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="movePseudoMap">
|
||||
<?php
|
||||
}
|
||||
elseif ( $monitor['CanMoveCon'] )
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveConMap">
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="control" value="moveConMap">
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<input type="hidden" name="scale" value="<?php echo $scale ?>">
|
||||
<input type="image" src="<?php echo $src ?>" width="<?php echo $width ?>" height="<?php echo $height ?>">
|
||||
</form>
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="scale" value="<?php echo $scale ?>">
|
||||
<input type="image" src="<?php echo $src ?>" width="<?php echo $width ?>" height="<?php echo $height ?>">
|
||||
</form>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Incoming args are shell-escaped. This function must escape any further arguments it cannot guarantee.
|
||||
|
@ -524,6 +495,11 @@ function getEventPath( $event )
|
|||
return( $eventPath );
|
||||
}
|
||||
|
||||
function getEventDefaultVideoPath( $event ) {
|
||||
$Event = new Event( $event );
|
||||
return $Event->getStreamSrc( array( "mode=mpeg&format=h264" ) );
|
||||
//$Event->Path().'/'.$event['DefaultVideo'];
|
||||
}
|
||||
|
||||
function deletePath( $path )
|
||||
{
|
||||
|
@ -537,7 +513,7 @@ function deletePath( $path )
|
|||
}
|
||||
}
|
||||
|
||||
function deleteEvent( $event, $mid=false ) {
|
||||
function deleteEvent( $event ) {
|
||||
|
||||
if ( empty($event) ) {
|
||||
Error( "Empty event passed to deleteEvent.");
|
||||
|
@ -545,50 +521,14 @@ function deleteEvent( $event, $mid=false ) {
|
|||
}
|
||||
|
||||
if ( gettype($event) != 'array' ) {
|
||||
# $event could be an eid, so turn it into an event hash
|
||||
$event = dbFetchOne( 'SELECT Id, MonitorId, StartTime FROM Events WHERE Id=?', NULL, array( $event ) );
|
||||
# $event could be an eid, so turn it into an event hash
|
||||
$event = new Event( $event );
|
||||
}
|
||||
|
||||
global $user;
|
||||
|
||||
if ( !$mid )
|
||||
$mid = $event['MonitorId'];
|
||||
|
||||
if ( $user['Events'] == 'Edit' ) {
|
||||
|
||||
dbQuery( 'DELETE FROM Events WHERE Id = ?', array($event['Id']) );
|
||||
if ( !ZM_OPT_FAST_DELETE ) {
|
||||
dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($event['Id']) );
|
||||
dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($event['Id']) );
|
||||
if ( ZM_USE_DEEP_STORAGE ) {
|
||||
|
||||
# Assumption: All events haev a start time
|
||||
$start_date = date_parse( $event['StartTime'] );
|
||||
$start_date['year'] = $start_date['year'] % 100;
|
||||
|
||||
# So this is because ZM creates a link under teh day pointing to the time that the event happened.
|
||||
$eventlink_path = sprintf('%s/%d/%02d/%02d/%02d/.%d', ZM_DIR_EVENTS, $mid, $start_date['year'], $start_date['month'], $start_date['day'], $event['Id'] );
|
||||
|
||||
if ( $id_files = glob( $eventlink_path ) ) {
|
||||
# I know we are using arrays here, but really there can only ever be 1 in the array
|
||||
$eventPath = preg_replace( '/\.'.$event['Id'].'$/', readlink($id_files[0]), $id_files[0] );
|
||||
deletePath( $eventPath );
|
||||
deletePath( $id_files[0] );
|
||||
$pathParts = explode( '/', $eventPath );
|
||||
for ( $i = count($pathParts)-1; $i >= 2; $i-- ) {
|
||||
$deletePath = join( '/', array_slice( $pathParts, 0, $i ) );
|
||||
if ( !glob( $deletePath."/*" ) ) {
|
||||
deletePath( $deletePath );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warning( "Found no event files under $eventlink_path" );
|
||||
} # end if found files
|
||||
} else {
|
||||
$eventPath = implode( '/', array( ZM_DIR_EVENTS, $mid, $event['Id'] ) );
|
||||
deletePath( $eventPath );
|
||||
} # USE_DEEP_STORAGE OR NOT
|
||||
} # ! ZM_OPT_FAST_DELETE
|
||||
$event->delete();
|
||||
} # CAN EDIT
|
||||
}
|
||||
|
||||
|
@ -642,6 +582,28 @@ function truncText( $text, $length, $deslash=1 )
|
|||
return( preg_replace( "/^(.{".$length.",}?)\b.*$/", "\\1…", ($deslash?stripslashes($text):$text) ) );
|
||||
}
|
||||
|
||||
function htmlSelect( $name, $contents, $values, $behaviours=false ) {
|
||||
|
||||
$behaviourText = "";
|
||||
if ( !empty($behaviours) ) {
|
||||
if ( is_array($behaviours) ) {
|
||||
foreach ( $behaviours as $event=>$action ) {
|
||||
$behaviourText .= ' '.$event.'="'.$action.'"';
|
||||
}
|
||||
} else {
|
||||
$behaviourText = ' onchange="'.$behaviours.'"';
|
||||
}
|
||||
}
|
||||
|
||||
$html = "<select name=\"$name\" id=\"$name\"$behaviourText>";
|
||||
foreach ( $contents as $value=>$text ) {
|
||||
$selected = is_array( $values ) ? in_array( $value, $values ) : $value==$values;
|
||||
$html .= "<option value=\"$value\"".($selected?" selected=\"selected\"":'').">$text</option>";
|
||||
}
|
||||
$html .= "</select>";
|
||||
return $html;
|
||||
}
|
||||
|
||||
function buildSelect( $name, $contents, $behaviours=false )
|
||||
{
|
||||
$value = "";
|
||||
|
@ -685,18 +647,18 @@ function buildSelect( $name, $contents, $behaviours=false )
|
|||
$behaviourText = ' onchange="'.$behaviours.'"';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<select name="<?php echo $name ?>" id="<?php echo $name ?>"<?php echo $behaviourText ?>>
|
||||
<?php
|
||||
?>
|
||||
<select name="<?php echo $name ?>" id="<?php echo $name ?>"<?php echo $behaviourText ?>>
|
||||
<?php
|
||||
foreach ( $contents as $contentValue => $contentText )
|
||||
{
|
||||
?>
|
||||
<option value="<?php echo $contentValue ?>"<?php if ( $value == $contentValue ) { ?> selected="selected"<?php } ?>><?php echo validHtmlStr($contentText) ?></option>
|
||||
<?php
|
||||
?>
|
||||
<option value="<?php echo $contentValue ?>"<?php if ( $value == $contentValue ) { ?> selected="selected"<?php } ?>><?php echo validHtmlStr($contentText) ?></option>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
?>
|
||||
</select>
|
||||
<?php
|
||||
$html = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
|
@ -1149,41 +1111,59 @@ function zmaCheck( $monitor )
|
|||
return( daemonCheck( "zma", "-m $monitor" ) );
|
||||
}
|
||||
|
||||
function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false )
|
||||
{
|
||||
require_once('includes/Storage.php');
|
||||
$eventPath = getEventPath( $event );
|
||||
function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) {
|
||||
$Storage = new Storage( $event['StorageId'] );
|
||||
$Event = new Event( $event );
|
||||
$eventPath = $Event->Path();
|
||||
|
||||
if ( !is_array($frame) )
|
||||
$frame = array( 'FrameId'=>$frame, 'Type'=>'' );
|
||||
|
||||
//echo "S:$scale, CO:$captureOnly<br>";
|
||||
|
||||
if ( file_exists( $eventPath.'/snapshot.jpg' ) ) {
|
||||
$captImage = "snapshot.jpg";
|
||||
} 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;
|
||||
Debug( "Running $command" );
|
||||
$output = array();
|
||||
$retval = 0;
|
||||
exec( $command, $output, $retval );
|
||||
Debug("Retval: $retval, output: " . implode("\n", $output));
|
||||
} else {
|
||||
Error("Can't create frame images from video becuase there is no video file for this event (".$Event->DefaultVideo() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$captPath = $eventPath.'/'.$captImage;
|
||||
if ( ! file_exists( $captPath ) ) {
|
||||
Error( "Capture file does not exist at $captPath" );
|
||||
return '';
|
||||
}
|
||||
$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 = $Storage->Path()."/".$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' ) )
|
||||
{
|
||||
if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) ) {
|
||||
$imagePath = $thumbPath = $isAnalImage?$analPath:$captPath;
|
||||
$imageFile = $Storage->Path()."/".$imagePath;
|
||||
$thumbFile = $Storage->Path()."/".$thumbPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
$imageFile = $imagePath;
|
||||
$thumbFile = $thumbPath;
|
||||
} else {
|
||||
if ( version_compare( phpversion(), "4.3.10", ">=") )
|
||||
$fraction = sprintf( "%.3F", $scale/SCALE_BASE );
|
||||
else
|
||||
|
@ -1193,19 +1173,15 @@ function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $ov
|
|||
$thumbCaptPath = preg_replace( "/\.jpg$/", "-$scale.jpg", $thumbCaptPath );
|
||||
$thumbAnalPath = preg_replace( "/\.jpg$/", "-$scale.jpg", $thumbAnalPath );
|
||||
|
||||
if ( $isAnalImage )
|
||||
{
|
||||
if ( $isAnalImage ) {
|
||||
$imagePath = $analPath;
|
||||
$thumbPath = $thumbAnalPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$imagePath = $captPath;
|
||||
$thumbPath = $thumbCaptPath;
|
||||
}
|
||||
|
||||
$imageFile = $Storage->Path()."/".$imagePath;
|
||||
//$thumbFile = ZM_DIR_EVENTS."/".$thumbPath;
|
||||
$imageFile = $imagePath;
|
||||
$thumbFile = $thumbPath;
|
||||
if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) )
|
||||
{
|
||||
|
@ -1235,8 +1211,6 @@ function getImageSrc( $event, $frame, $scale=SCALE_BASE, $captureOnly=false, $ov
|
|||
'hasAnalImage' => $hasAnalImage,
|
||||
);
|
||||
|
||||
//echo "IP:$imagePath<br>";
|
||||
//echo "TP:$thumbPath<br>";
|
||||
return( $imageData );
|
||||
}
|
||||
|
||||
|
@ -1256,6 +1230,7 @@ function viewImagePath( $path, $querySep='&' )
|
|||
|
||||
function createListThumbnail( $event, $overwrite=false )
|
||||
{
|
||||
# Load the frame with the highest score to use as a thumbnail
|
||||
if ( !($frame = dbFetchOne( "SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1", NULL, array( $event['Id'], $event['MaxScore'] ) )) )
|
||||
return( false );
|
||||
|
||||
|
@ -1279,6 +1254,9 @@ function createListThumbnail( $event, $overwrite=false )
|
|||
}
|
||||
|
||||
$imageData = getImageSrc( $event, $frame, $scale, false, $overwrite );
|
||||
if ( ! $imageData ) {
|
||||
return ( false );
|
||||
}
|
||||
$thumbData = $frame;
|
||||
$thumbData['Path'] = $imageData['thumbPath'];
|
||||
$thumbData['Width'] = (int)$thumbWidth;
|
||||
|
@ -1441,6 +1419,9 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' )
|
|||
case 'MonitorName':
|
||||
$filter['sql'] .= 'M.'.preg_replace( '/^Monitor/', '', $filter['terms'][$i]['attr'] );
|
||||
break;
|
||||
case 'ServerId':
|
||||
$filter['sql'] .= 'M.ServerId';
|
||||
break;
|
||||
case 'DateTime':
|
||||
$filter['sql'] .= "E.StartTime";
|
||||
break;
|
||||
|
@ -1456,6 +1437,7 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' )
|
|||
case 'Id':
|
||||
case 'Name':
|
||||
case 'MonitorId':
|
||||
case 'StorageId':
|
||||
case 'Length':
|
||||
case 'Frames':
|
||||
case 'AlarmFrames':
|
||||
|
@ -1488,6 +1470,12 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' )
|
|||
case 'Notes':
|
||||
$value = dbEscape($value);
|
||||
break;
|
||||
case 'ServerId':
|
||||
if ( $value == 'ZM_SERVER_ID' ) {
|
||||
$value = ZM_SERVER_ID;
|
||||
} else {
|
||||
$value = dbEscape($value);
|
||||
}
|
||||
case 'DateTime':
|
||||
$value = "'".strftime( STRF_FMT_DATETIME_DB, strtotime( $value ) )."'";
|
||||
break;
|
||||
|
@ -1705,9 +1693,9 @@ function sidField()
|
|||
if ( SID )
|
||||
{
|
||||
list( $sessname, $sessid ) = explode( "=", SID );
|
||||
?>
|
||||
<input type="hidden" name="<?php echo $sessname ?>" value="<?php echo $sessid ?>"/>
|
||||
<?php
|
||||
?>
|
||||
<input type="hidden" name="<?php echo $sessname ?>" value="<?php echo $sessid ?>"/>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2506,11 +2494,11 @@ function getStreamHTML( $monitor, $scale=100 ) {
|
|||
//FIXME, the width and height of the image need to be scaled.
|
||||
if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
|
||||
$streamSrc = $monitor->getStreamSrc( array( "mode=mpeg", "scale=".$scale, "bitrate=".ZM_WEB_VIDEO_BITRATE, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "format=".ZM_MPEG_LIVE_FORMAT ) );
|
||||
outputVideoStream( "liveStream", $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), ZM_MPEG_LIVE_FORMAT, $monitor->Name() );
|
||||
return getVideoStream( "liveStream", $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), ZM_MPEG_LIVE_FORMAT, $monitor->Name() );
|
||||
} else if ( canStream() ) {
|
||||
$streamSrc = $monitor->getStreamSrc( array( 'mode=jpeg', 'scale='.$scale, 'maxfps='.ZM_WEB_VIDEO_MAXFPS, 'buffer='.$monitor->StreamReplayBuffer() ) );
|
||||
if ( canStreamNative() )
|
||||
outputImageStream( "liveStream", $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), $monitor->Name() );
|
||||
return getImageStream( "liveStream", $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), $monitor->Name() );
|
||||
elseif ( canStreamApplet() )
|
||||
outputHelperStream( "liveStream", $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), $monitor->Name() );
|
||||
} else {
|
||||
|
|
|
@ -49,6 +49,7 @@ if ( false )
|
|||
require_once( 'includes/config.php' );
|
||||
require_once( 'includes/logger.php' );
|
||||
require_once( 'includes/Server.php' );
|
||||
require_once( 'includes/Event.php' );
|
||||
require_once( 'includes/Monitor.php' );
|
||||
|
||||
if ( isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' )
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
console.log('zoomrotate: Start');
|
||||
|
||||
(function(){
|
||||
var defaults, extend;
|
||||
console.log('zoomrotate: Init defaults');
|
||||
defaults = {
|
||||
zoom: 1,
|
||||
rotate: 0
|
||||
};
|
||||
console.log('zoomrotate: Init Extend');
|
||||
extend = function() {
|
||||
var args, target, i, object, property;
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
target = args.shift() || {};
|
||||
for (i in args) {
|
||||
object = args[i];
|
||||
for (property in object) {
|
||||
if (object.hasOwnProperty(property)) {
|
||||
if (typeof object[property] === 'object') {
|
||||
target[property] = extend(target[property], object[property]);
|
||||
} else {
|
||||
target[property] = object[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
/**
|
||||
* register the zoomrotate plugin
|
||||
*/
|
||||
videojs.plugin('zoomrotate', function(options){
|
||||
console.log('zoomrotate: Register init');
|
||||
var settings, player, video, poster;
|
||||
settings = extend(defaults, options);
|
||||
|
||||
/* Grab the necessary DOM elements */
|
||||
player = this.el();
|
||||
video = this.el().getElementsByTagName('video')[0];
|
||||
poster = this.el().getElementsByTagName('div')[1]; // div vjs-poster
|
||||
|
||||
console.log('zoomrotate: '+video.style);
|
||||
console.log('zoomrotate: '+poster.style);
|
||||
console.log('zoomrotate: '+options.rotate);
|
||||
console.log('zoomrotate: '+options.zoom);
|
||||
|
||||
/* Array of possible browser specific settings for transformation */
|
||||
var properties = ['transform', 'WebkitTransform', 'MozTransform',
|
||||
'msTransform', 'OTransform'],
|
||||
prop = properties[0];
|
||||
|
||||
/* Iterators */
|
||||
var i,j;
|
||||
|
||||
/* Find out which CSS transform the browser supports */
|
||||
for(i=0,j=properties.length;i<j;i++){
|
||||
if(typeof player.style[properties[i]] !== 'undefined'){
|
||||
prop = properties[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Let's do it */
|
||||
player.style.overflow = 'hidden';
|
||||
video.style[prop]='scale('+options.zoom+') rotate('+options.rotate+'deg)';
|
||||
poster.style[prop]='scale('+options.zoom+') rotate('+options.rotate+'deg)';
|
||||
console.log('zoomrotate: Register end');
|
||||
});
|
||||
})();
|
||||
|
||||
console.log('zoomrotate: End');
|
|
@ -545,6 +545,7 @@ $SLANG = array(
|
|||
'OpNe' => 'not equal to',
|
||||
'OpNotIn' => 'not in set',
|
||||
'OpNotMatches' => 'does not match',
|
||||
'OptionalEncoderParam' => 'Optional Encoder Parameters',
|
||||
'OptionHelp' => 'Option Help',
|
||||
'OptionRestartWarning' => 'These changes may not come into effect fully\nwhile the system is running. When you have\nfinished making your changes please ensure that\nyou restart ZoneMinder.',
|
||||
'Options' => 'Options',
|
||||
|
@ -585,6 +586,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',
|
||||
|
@ -619,6 +621,7 @@ $SLANG = array(
|
|||
'RunState' => 'Run State',
|
||||
'SaveAs' => 'Save as',
|
||||
'SaveFilter' => 'Save Filter',
|
||||
'SaveJPEGs' => 'Save JPEGs',
|
||||
'Save' => 'Save',
|
||||
'Scale' => 'Scale',
|
||||
'Score' => 'Score',
|
||||
|
@ -727,6 +730,7 @@ $SLANG = array(
|
|||
'VideoGenParms' => 'Video Generation Parameters',
|
||||
'VideoGenSucceeded' => 'Video Generation Succeeded!',
|
||||
'VideoSize' => 'Video Size',
|
||||
'VideoWriter' => 'Video Writer',
|
||||
'Video' => 'Video',
|
||||
'ViewAll' => 'View All',
|
||||
'ViewEvent' => 'View Event',
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
.ptzControls {
|
||||
vertical-align: top;
|
||||
margin: 10px auto 0;
|
||||
width: 300px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.ptzControls input.ptzTextBtn {
|
||||
margin-top: 2px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel {
|
||||
|
@ -72,6 +71,8 @@
|
|||
.ptzControls .controlsPanel .pantiltPanel {
|
||||
margin: 0 auto;
|
||||
height: 180px;
|
||||
float: left;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .pantiltButtons {
|
||||
|
@ -79,8 +80,8 @@
|
|||
border: 1px solid #006699;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
@ -126,12 +127,12 @@
|
|||
background: url("../../graphics/arrow-dr.gif") no-repeat 0 0;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .powerControls {
|
||||
margin: 0 auto;
|
||||
.ptzControls .controlsPanel .powerControls {
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls {
|
||||
margin: 0 auto;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls input {
|
||||
|
|
|
@ -23,10 +23,9 @@
|
|||
|
||||
body {
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 10px;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -84,10 +83,6 @@ a:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
input,textarea,select,button {
|
||||
border: 1px #7f7fb2 solid;
|
||||
font-family: inherit;
|
||||
|
@ -190,12 +185,6 @@ ul.tabList li.active a {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#content table.major th, #content table.major td {
|
||||
border: 1px solid #7f7fb2;
|
||||
padding: 3px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#content table.major th {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
@ -438,7 +427,26 @@ th.table-th-sort-rev span.table-th-sort-span {
|
|||
}
|
||||
|
||||
#footer {
|
||||
width: 96%;
|
||||
margin: 8px auto;
|
||||
border-top: 1px solid #e7e7e7;
|
||||
margin: 32px 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.nav.nav-pills.nav-stacked.col-md-2 {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nav.nav-pills.nav-stacked.col-md-2 > li > a {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
#options {
|
||||
border-left: 1px solid #337ab7;
|
||||
}
|
||||
|
||||
#options .form-group {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
|
||||
}
|
||||
|
|
|
@ -57,6 +57,23 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
#videoBar1 div {
|
||||
text-align: center;
|
||||
float: center;
|
||||
}
|
||||
|
||||
#videoBar1 #prevEvent {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#videoBar1 #dlEvent {
|
||||
float: center;
|
||||
}
|
||||
|
||||
#videoBar1 #nextEvent {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#imageFeed {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
input.small {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
input.medium {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
input.large {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
#contentTable.optionTable th, #contentTable.optionTable td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#contentTable.userTable th, #contentTable.userTable td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#contentTable.userTable .colMonitor, #contentTable.userTable .colUsername {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
.ptzControls {
|
||||
vertical-align: top;
|
||||
margin: 10px auto 0;
|
||||
width: 300px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.ptzControls input.ptzTextBtn {
|
||||
margin-top: 2px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel {
|
||||
|
@ -72,6 +71,8 @@
|
|||
.ptzControls .controlsPanel .pantiltPanel {
|
||||
margin: 0 auto;
|
||||
height: 180px;
|
||||
float: left;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .pantiltButtons {
|
||||
|
@ -79,8 +80,8 @@
|
|||
border: 1px solid #006699;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
@ -126,12 +127,12 @@
|
|||
background: url("../../graphics/arrow-dr.gif") no-repeat 0 0;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .powerControls {
|
||||
margin: 0 auto;
|
||||
.ptzControls .controlsPanel .powerControls {
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls {
|
||||
margin: 0 auto;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls input {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
#menuBar1 {
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
padding: 3px 0;
|
||||
text-align: center;
|
||||
clear: both;
|
||||
|
@ -41,7 +40,6 @@
|
|||
|
||||
#menuBar2 {
|
||||
width: 100%;
|
||||
height: 1.2em;
|
||||
padding: 3px 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
@ -57,6 +55,16 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
#menuBar1:after,
|
||||
#menuBar2:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#imageFeed {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
.ptzControls {
|
||||
vertical-align: top;
|
||||
margin: 10px auto 0;
|
||||
width: 300px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.ptzControls input.ptzTextBtn {
|
||||
margin-top: 2px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel {
|
||||
|
@ -72,6 +71,8 @@
|
|||
.ptzControls .controlsPanel .pantiltPanel {
|
||||
margin: 0 auto;
|
||||
height: 180px;
|
||||
float: left;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .pantiltButtons {
|
||||
|
@ -79,8 +80,8 @@
|
|||
border: 1px solid #006699;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
@ -126,12 +127,12 @@
|
|||
background: url("../../graphics/arrow-dr.gif") no-repeat 0 0;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .powerControls {
|
||||
margin: 0 auto;
|
||||
.ptzControls .controlsPanel .powerControls {
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls {
|
||||
margin: 0 auto;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.ptzControls .presetControls input {
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
|
||||
#menuBar1 {
|
||||
width: 100%;
|
||||
height: 1.5em;
|
||||
padding: 3px 0;
|
||||
text-align: center;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
#menuBar1 #nameControl {
|
||||
float: left;
|
||||
}
|
||||
|
@ -41,7 +41,6 @@
|
|||
|
||||
#menuBar2 {
|
||||
width: 100%;
|
||||
height: 1.2em;
|
||||
padding: 3px 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
@ -56,6 +55,15 @@
|
|||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
#menuBar1:after,
|
||||
#menuBar2:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#imageFeed {
|
||||
text-align: center;
|
||||
|
@ -236,3 +244,64 @@
|
|||
height: 10px;
|
||||
background-color: #444444;
|
||||
}
|
||||
#eventVideo {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#video-controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity .3s;
|
||||
-moz-transition: opacity .3s;
|
||||
-o-transition: opacity .3s;
|
||||
-ms-transition: opacity .3s;
|
||||
transition: opacity .3s;
|
||||
background-image: linear-gradient(bottom, rgb(3,113,168) 13%, rgb(0,136,204) 100%);
|
||||
background-image: -o-linear-gradient(bottom, rgb(3,113,168) 13%, rgb(0,136,204) 100%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(3,113,168) 13%, rgb(0,136,204) 100%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(3,113,168) 13%, rgb(0,136,204) 100%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(3,113,168) 13%, rgb(0,136,204) 100%);
|
||||
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.13, rgb(3,113,168)),
|
||||
color-stop(1, rgb(0,136,204))
|
||||
);
|
||||
}
|
||||
|
||||
#eventVideo:hover #video-controls {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
button {
|
||||
background: rgba(0,0,0,.5);
|
||||
border: 0;
|
||||
color: #EEE;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#seekbar {
|
||||
width: 360px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#volume-bar {
|
||||
width: 60px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['PresetGoto'] = "presetGoto";
|
||||
$cmds['PresetHome'] = "presetHome";
|
||||
|
||||
if ( !empty($monitor->CanZoom) )
|
||||
if ( !empty($monitor->CanZoom()) )
|
||||
{
|
||||
if ( $monitor->CanZoomCon() )
|
||||
$cmds['ZoomRoot'] = "zoomCon";
|
||||
|
@ -45,7 +45,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['ZoomMan'] = "zoomMan";
|
||||
}
|
||||
|
||||
if ( !empty($monitor->CanFocus) )
|
||||
if ( !empty($monitor->CanFocus()) )
|
||||
{
|
||||
if ( $monitor->CanFocusCon() )
|
||||
$cmds['FocusRoot'] = "focusCon";
|
||||
|
@ -60,7 +60,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['FocusMan'] = "focusMan";
|
||||
}
|
||||
|
||||
if ( !empty($monitor->CanIris) )
|
||||
if ( !empty($monitor->CanIris()) )
|
||||
{
|
||||
if ( $monitor->CanIrisCon() )
|
||||
$cmds['IrisRoot'] = "irisCon";
|
||||
|
@ -75,7 +75,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['IrisMan'] = "irisMan";
|
||||
}
|
||||
|
||||
if ( !empty($monitor->CanWhite) )
|
||||
if ( !empty($monitor->CanWhite()) )
|
||||
{
|
||||
if ( $monitor->CanWhiteCon() )
|
||||
$cmds['WhiteRoot'] = "whiteCon";
|
||||
|
@ -89,7 +89,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['WhiteMan'] = "whiteMan";
|
||||
}
|
||||
|
||||
if ( !empty($monitor->CanGain) )
|
||||
if ( !empty($monitor->CanGain()) )
|
||||
{
|
||||
if ( $monitor->CanGainCon() )
|
||||
$cmds['GainRoot'] = "gainCon";
|
||||
|
@ -103,7 +103,7 @@ function getControlCommands( $monitor )
|
|||
$cmds['GainMan'] = "gainMan";
|
||||
}
|
||||
|
||||
if ( !empty($monitor->CanMove) )
|
||||
if ( !empty($monitor->CanMove()) )
|
||||
{
|
||||
if ( $monitor->CanMoveCon() )
|
||||
{
|
||||
|
@ -243,12 +243,12 @@ function controlPanTilt( $monitor, $cmds )
|
|||
ob_start();
|
||||
?>
|
||||
<div class="pantiltControls">
|
||||
<div class="pantilLabel"><?php echo translate('PanTilt') ?></div>
|
||||
<div class="pantiltLabel"><?php echo translate('PanTilt') ?></div>
|
||||
<div class="pantiltButtons">
|
||||
<?php
|
||||
$hasPan = $monitor->CanPan;
|
||||
$hasTilt = $monitor->CanTilt;
|
||||
$hasDiag = $hasPan && $hasTilt && $monitor->CanMoveDiag;
|
||||
$hasPan = $monitor->CanPan();
|
||||
$hasTilt = $monitor->CanTilt();
|
||||
$hasDiag = $hasPan && $hasTilt && $monitor->CanMoveDiag();
|
||||
?>
|
||||
<div class="arrowBtn upLeftBtn<?php echo $hasDiag?'':' invisible' ?>" onclick="controlCmd('<?php echo $cmds['MoveUpLeft'] ?>',event,-1,-1)"></div>
|
||||
<div class="arrowBtn upBtn<?php echo $hasTilt?'':' invisible' ?>" onclick="controlCmd('<?php echo $cmds['MoveUp'] ?>',event,0,-1)"></div>
|
||||
|
@ -278,7 +278,7 @@ function controlPresets( $monitor, $cmds )
|
|||
$labels[$row['Preset']] = $row['Label'];
|
||||
}
|
||||
|
||||
$presetBreak = (int)(($monitor->NumPresets+1)/((int)(($monitor->NumPresets-1)/MAX_PRESETS)+1));
|
||||
$presetBreak = (int)(($monitor->NumPresets()+1)/((int)(($monitor->NumPresets()-1)/MAX_PRESETS)+1));
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
|
@ -286,7 +286,7 @@ function controlPresets( $monitor, $cmds )
|
|||
<!--<div><?php echo translate('Presets') ?></div>-->
|
||||
<div>
|
||||
<?php
|
||||
for ( $i = 1; $i <= $monitor->NumPresets; $i++ )
|
||||
for ( $i = 1; $i <= $monitor->NumPresets(); $i++ )
|
||||
{
|
||||
?><input type="button" class="ptzNumBtn" title="<?php echo isset($labels[$i])?$labels[$i]:"" ?>" value="<?php echo $i ?>" onclick="controlCmd('<?php echo $cmds['PresetGoto'] ?><?php echo $i ?>');"/><?php
|
||||
if ( $i && (($i%$presetBreak) == 0) )
|
||||
|
@ -367,24 +367,22 @@ function ptzControls( $monitor )
|
|||
echo controlIris( $monitor, $cmds );
|
||||
if ( $monitor->CanWhite() )
|
||||
echo controlWhite( $monitor, $cmds );
|
||||
if ( $monitor->CanMove() || ( $monitor->CanWake() || $monitor->CanSleep() || $monitor->CanReset() ) )
|
||||
{
|
||||
if ( $monitor->CanMove() ) {
|
||||
?>
|
||||
<div class="pantiltPanel">
|
||||
<?php
|
||||
if ( $monitor->CanMove() )
|
||||
echo controlPanTilt( $monitor, $cmds );
|
||||
if ( $monitor->CanWake() || $monitor->CanSleep() || $monitor->CanReset() )
|
||||
echo controlPower( $monitor, $cmds );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
if ( $monitor->CanWake() || $monitor->CanSleep() || $monitor->CanReset() )
|
||||
echo controlPower( $monitor, $cmds );
|
||||
if ( $monitor->HasPresets() )
|
||||
echo controlPresets( $monitor, $cmds );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
if ( $monitor->HasPresets() )
|
||||
echo controlPresets( $monitor, $cmds );
|
||||
return( ob_get_clean() );
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -589,7 +589,6 @@ else if (document.layers) window.onload=start_slider;
|
|||
|
||||
function exportEventImagesMaster( $eids )
|
||||
{
|
||||
global $SLANG;
|
||||
ob_start();
|
||||
exportHeader( translate('Images').' Master' );
|
||||
?>
|
||||
|
|
|
@ -44,6 +44,7 @@ function xhtmlHeaders( $file, $title )
|
|||
<link rel="shortcut icon" href="graphics/favicon.ico"/>
|
||||
<link rel="stylesheet" href="css/reset.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="css/overlay.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="<?php echo $skinCssFile ?>" type="text/css" media="screen"/>
|
||||
<?php
|
||||
if ( $viewCssFile )
|
||||
|
@ -70,7 +71,10 @@ function xhtmlHeaders( $file, $title )
|
|||
<script type="text/javascript" src="js/mootools.ext.js"></script>
|
||||
<script type="text/javascript" src="js/logger.js"></script>
|
||||
<script type="text/javascript" src="js/overlay.js"></script>
|
||||
<?php if ( $title == 'Login' && (defined('ZM_OPT_USE_GOOG_RECAPTCHA') && ZM_OPT_USE_GOOG_RECAPTCHA) ) { ?>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
<?php
|
||||
}
|
||||
if ( $skinJsPhpFile )
|
||||
{
|
||||
?>
|
||||
|
|
|
@ -37,7 +37,7 @@ var popupSizes = {
|
|||
'eventdetail': { 'width': 600, 'height': 220 },
|
||||
'events': { 'width': 960, 'height': 780 },
|
||||
'export': { 'width': 400, 'height': 340 },
|
||||
'filter': { 'width': 720, 'height': 360 },
|
||||
'filter': { 'width': 820, 'height': 360 },
|
||||
'filtersave': { 'width': 610, 'height': 120 },
|
||||
'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 100 },
|
||||
'frames': { 'width': 500, 'height': 600 },
|
||||
|
@ -48,7 +48,7 @@ var popupSizes = {
|
|||
'log': { 'width': 1080, 'height': 720 },
|
||||
'login': { 'width': 720, 'height': 480 },
|
||||
'logout': { 'width': 260, 'height': 100 },
|
||||
'monitor': { 'width': 450, 'height': 440 },
|
||||
'monitor': { 'width': 700, 'height': 640 },
|
||||
'monitorpreset':{ 'width': 440, 'height': 200 },
|
||||
'monitorprobe': { 'width': 500, 'height': 240 },
|
||||
'monitorselect':{ 'width': 160, 'height': 200 },
|
||||
|
|
|
@ -37,11 +37,11 @@ var popupSizes = {
|
|||
'eventdetail': { 'width': 600, 'height': 220 },
|
||||
'events': { 'width': 960, 'height': 780 },
|
||||
'export': { 'width': 400, 'height': 340 },
|
||||
'filter': { 'width': 800, 'height': 600 },
|
||||
'filter': { 'width': 900, 'height': 600 },
|
||||
'filtersave': { 'width': 610, 'height': 220 },
|
||||
'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 100 },
|
||||
'frames': { 'width': 500, 'height': 600 },
|
||||
'function': { 'width': 300, 'height': 92 },
|
||||
'function': { 'width': 300, 'height': 160 },
|
||||
'group': { 'width': 360, 'height': 300 },
|
||||
'groups': { 'width': 540, 'height': 420 },
|
||||
'image': { 'addWidth': 48, 'addHeight': 80 },
|
||||
|
|
|
@ -35,20 +35,20 @@ var popupSizes = {
|
|||
'donate': { 'width': 500, 'height': 280 },
|
||||
'event': { 'addWidth': 108, 'minWidth': 496, 'addHeight': 230, minHeight: 540 },
|
||||
'eventdetail': { 'width': 600, 'height': 220 },
|
||||
'events': { 'width': 1080, 'height': 780 },
|
||||
'events': { 'width': 1220, 'height': 780 },
|
||||
'export': { 'width': 400, 'height': 340 },
|
||||
'filter': { 'width': 800, 'height': 600 },
|
||||
'filter': { 'width': 900, 'height': 600 },
|
||||
'filtersave': { 'width': 610, 'height': 220 },
|
||||
'frame': { 'addWidth': 32, 'minWidth': 384, 'addHeight': 100 },
|
||||
'frames': { 'width': 500, 'height': 600 },
|
||||
'function': { 'width': 300, 'height': 140 },
|
||||
'function': { 'width': 300, 'height': 160 },
|
||||
'group': { 'width': 360, 'height': 300 },
|
||||
'groups': { 'width': 540, 'height': 420 },
|
||||
'image': { 'addWidth': 48, 'addHeight': 80 },
|
||||
'log': { 'width': 1080, 'height': 720 },
|
||||
'login': { 'width': 720, 'height': 480 },
|
||||
'logout': { 'width': 260, 'height': 150 },
|
||||
'monitor': { 'width': 600, 'height': 780 },
|
||||
'monitor': { 'width': 800, 'height': 780 },
|
||||
'monitorpreset':{ 'width': 440, 'height': 200 },
|
||||
'monitorprobe': { 'width': 500, 'height': 240 },
|
||||
'monitorselect':{ 'width': 160, 'height': 200 },
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue