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 = $updateUnclosedEventsSth->execute
|
||||
(
|
||||
sprintf("%s%d%s",
|
||||
$event->{Prefix},
|
||||
$event->{Id},
|
||||
RECOVER_TAG
|
||||
),
|
||||
$event->{EndTime},
|
||||
$event->{Length},
|
||||
$event->{Frames},
|
||||
$event->{AlarmFrames},
|
||||
$event->{TotScore},
|
||||
$event->{AlarmFrames}
|
||||
? int($event->{TotScore} / $event->{AlarmFrames})
|
||||
: 0
|
||||
,
|
||||
$event->{MaxScore},
|
||||
RECOVER_TEXT,
|
||||
$event->{Id}
|
||||
) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() );
|
||||
$res = $selectFrameDataSth->execute( $event->{Id} );
|
||||
my $frame = $selectFrameDataSth->fetchrow_hashref();
|
||||
if ( $frame ) {
|
||||
$res = $updateUnclosedEventsSth->execute
|
||||
(
|
||||
sprintf("%s%d%s",
|
||||
$Monitors{$event->{MonitorId}}->{EventPrefix},
|
||||
$event->{Id},
|
||||
RECOVER_TAG
|
||||
),
|
||||
$frame->{EndTime},
|
||||
$frame->{EndTimeStamp} - $event->{TimeStamp},
|
||||
$frame->{Frames},
|
||||
$frame->{AlarmFrames},
|
||||
$frame->{TotScore},
|
||||
$frame->{AlarmFrames}
|
||||
? int($frame->{TotScore} / $frame->{AlarmFrames})
|
||||
: 0
|
||||
,
|
||||
$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,52 +312,56 @@ while( 1 )
|
|||
|
||||
# Check for alarms that might have happened
|
||||
my @out_messages;
|
||||
foreach my $monitor ( values(%monitors) )
|
||||
{
|
||||
my ( $state, $last_event )
|
||||
= zmMemRead( $monitor,
|
||||
[ "shared_data:state",
|
||||
"shared_data:last_event"
|
||||
]
|
||||
);
|
||||
foreach my $monitor ( values(%monitors) ) {
|
||||
|
||||
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" );
|
||||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
||||
if ( $state == STATE_ALARM
|
||||
|| $state == STATE_ALERT
|
||||
) # In alarm state
|
||||
{
|
||||
if ( !defined($monitor->{LastEvent})
|
||||
|| ($last_event != $monitor->{LastEvent})
|
||||
) # A new event
|
||||
{
|
||||
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",
|
||||
"shared_data:last_event"
|
||||
]
|
||||
);
|
||||
|
||||
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" );
|
||||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
||||
if ( $state == STATE_ALARM
|
||||
|| $state == STATE_ALERT
|
||||
) { # In alarm state
|
||||
if ( !defined($monitor->{LastEvent})
|
||||
|| ($last_event != $monitor->{LastEvent})
|
||||
) { # A new event
|
||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
||||
} else { # The same one as last time, so ignore it
|
||||
# Do nothing
|
||||
}
|
||||
} elsif ( ($state == STATE_IDLE
|
||||
&& $monitor->{LastState} != STATE_IDLE
|
||||
)
|
||||
|| ($state == STATE_TAPE
|
||||
&& $monitor->{LastState} != STATE_TAPE
|
||||
)
|
||||
) { # 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
|
||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
||||
push( @out_messages, $monitor->{Id}."|off|".time()."|".$last_event );
|
||||
}
|
||||
else # The same one as last time, so ignore it
|
||||
{
|
||||
# Do nothing
|
||||
}
|
||||
$monitor->{LastState} = $state;
|
||||
$monitor->{LastEvent} = $last_event;
|
||||
} else { # Our attempt to verify the memory handle failed. We should reload the monitors.
|
||||
$needsReload = 1;
|
||||
}
|
||||
elsif ( ($state == STATE_IDLE
|
||||
&& $monitor->{LastState} != STATE_IDLE
|
||||
)
|
||||
|| ($state == STATE_TAPE
|
||||
&& $monitor->{LastState} != STATE_TAPE
|
||||
)
|
||||
) # 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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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,30 +20,31 @@
|
|||
#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 ) :
|
||||
id( p_id ),
|
||||
type( p_type ),
|
||||
width( p_width),
|
||||
height( p_height ),
|
||||
colours( p_colours ),
|
||||
subpixelorder( p_subpixelorder ),
|
||||
brightness( p_brightness ),
|
||||
hue( p_hue ),
|
||||
colour( p_colour ),
|
||||
contrast( p_contrast ),
|
||||
capture( 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),
|
||||
height( p_height ),
|
||||
colours( p_colours ),
|
||||
subpixelorder( p_subpixelorder ),
|
||||
brightness( p_brightness ),
|
||||
hue( p_hue ),
|
||||
colour( p_colour ),
|
||||
contrast( p_contrast ),
|
||||
capture( p_capture ),
|
||||
record_audio( p_record_audio )
|
||||
{
|
||||
pixels = width * height;
|
||||
imagesize = pixels * colours;
|
||||
|
||||
Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",id,width,height,colours,subpixelorder,capture);
|
||||
|
||||
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */
|
||||
if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 16) != 0) {
|
||||
Fatal("Image size is not multiples of 16");
|
||||
} else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 16) != 0 || (imagesize % 12) != 0)) {
|
||||
Fatal("Image size is not multiples of 12 and 16");
|
||||
}
|
||||
pixels = width * height;
|
||||
imagesize = pixels * colours;
|
||||
|
||||
Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",id,width,height,colours,subpixelorder,capture);
|
||||
|
||||
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */
|
||||
if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 16) != 0) {
|
||||
Fatal("Image size is not multiples of 16");
|
||||
} else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 16) != 0 || (imagesize % 12) != 0)) {
|
||||
Fatal("Image size is not multiples of 12 and 16");
|
||||
}
|
||||
}
|
||||
|
||||
Camera::~Camera()
|
||||
|
|
|
@ -32,52 +32,56 @@
|
|||
class Camera
|
||||
{
|
||||
protected:
|
||||
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType;
|
||||
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType;
|
||||
|
||||
int id;
|
||||
SourceType type;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int colours;
|
||||
unsigned int subpixelorder;
|
||||
unsigned int pixels;
|
||||
unsigned int imagesize;
|
||||
int brightness;
|
||||
int hue;
|
||||
int colour;
|
||||
int contrast;
|
||||
bool capture;
|
||||
int id;
|
||||
SourceType type;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int colours;
|
||||
unsigned int subpixelorder;
|
||||
unsigned int pixels;
|
||||
unsigned int imagesize;
|
||||
int brightness;
|
||||
int hue;
|
||||
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 );
|
||||
virtual ~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 );
|
||||
virtual ~Camera();
|
||||
|
||||
int getId() const { return( id ); }
|
||||
SourceType Type() const { return( type ); }
|
||||
bool IsLocal() const { return( type == LOCAL_SRC ); }
|
||||
bool IsRemote() const { return( type == REMOTE_SRC ); }
|
||||
bool IsFile() const { return( type == FILE_SRC ); }
|
||||
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); }
|
||||
bool IsLibvlc() const { return( type == LIBVLC_SRC ); }
|
||||
bool IscURL() const { return( type == CURL_SRC ); }
|
||||
unsigned int Width() const { return( width ); }
|
||||
unsigned int Height() const { return( height ); }
|
||||
unsigned int Colours() const { return( colours ); }
|
||||
unsigned int SubpixelOrder() const { return( subpixelorder ); }
|
||||
unsigned int Pixels() const { return( pixels ); }
|
||||
unsigned int ImageSize() const { return( imagesize ); }
|
||||
int getId() const { return( id ); }
|
||||
SourceType Type() const { return( type ); }
|
||||
bool IsLocal() const { return( type == LOCAL_SRC ); }
|
||||
bool IsRemote() const { return( type == REMOTE_SRC ); }
|
||||
bool IsFile() const { return( type == FILE_SRC ); }
|
||||
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); }
|
||||
bool IsLibvlc() const { return( type == LIBVLC_SRC ); }
|
||||
bool IscURL() const { return( type == CURL_SRC ); }
|
||||
unsigned int Width() const { return( width ); }
|
||||
unsigned int Height() const { return( height ); }
|
||||
unsigned int Colours() const { return( colours ); }
|
||||
unsigned int SubpixelOrder() const { return( subpixelorder ); }
|
||||
unsigned int Pixels() const { return( pixels ); }
|
||||
unsigned int ImageSize() const { return( imagesize ); }
|
||||
|
||||
virtual int Brightness( int/*p_brightness*/=-1 ) { return( -1 ); }
|
||||
virtual int Hue( int/*p_hue*/=-1 ) { return( -1 ); }
|
||||
virtual int Colour( int/*p_colour*/=-1 ) { return( -1 ); }
|
||||
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
|
||||
virtual int Brightness( int/*p_brightness*/=-1 ) { return( -1 ); }
|
||||
virtual int Hue( int/*p_hue*/=-1 ) { return( -1 ); }
|
||||
virtual int Colour( int/*p_colour*/=-1 ) { return( -1 ); }
|
||||
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
|
||||
|
||||
bool CanCapture() const { return( capture ); }
|
||||
|
||||
virtual int PrimeCapture() { return( 0 ); }
|
||||
virtual int PreCapture()=0;
|
||||
virtual int Capture( Image &image )=0;
|
||||
virtual int PostCapture()=0;
|
||||
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
|
||||
|
|
|
@ -29,277 +29,279 @@
|
|||
|
||||
void zmLoadConfig()
|
||||
{
|
||||
FILE *cfg;
|
||||
char line[512];
|
||||
if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL )
|
||||
{
|
||||
Fatal( "Can't open %s: %s", ZM_CONFIG, strerror(errno) );
|
||||
}
|
||||
while ( fgets( line, sizeof(line), cfg ) != NULL )
|
||||
{
|
||||
char *line_ptr = line;
|
||||
FILE *cfg;
|
||||
char line[512];
|
||||
if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL )
|
||||
{
|
||||
Fatal( "Can't open %s: %s", ZM_CONFIG, strerror(errno) );
|
||||
}
|
||||
while ( fgets( line, sizeof(line), cfg ) != NULL )
|
||||
{
|
||||
char *line_ptr = line;
|
||||
|
||||
// Trim off any cr/lf line endings
|
||||
int chomp_len = strcspn( line_ptr, "\r\n" );
|
||||
line_ptr[chomp_len] = '\0';
|
||||
// Trim off any cr/lf line endings
|
||||
int chomp_len = strcspn( line_ptr, "\r\n" );
|
||||
line_ptr[chomp_len] = '\0';
|
||||
|
||||
// Remove leading white space
|
||||
int white_len = strspn( line_ptr, " \t" );
|
||||
line_ptr += white_len;
|
||||
// Remove leading white space
|
||||
int white_len = strspn( line_ptr, " \t" );
|
||||
line_ptr += white_len;
|
||||
|
||||
// Check for comment or empty line
|
||||
if ( *line_ptr == '\0' || *line_ptr == '#' )
|
||||
continue;
|
||||
// Check for comment or empty line
|
||||
if ( *line_ptr == '\0' || *line_ptr == '#' )
|
||||
continue;
|
||||
|
||||
// Remove trailing white space
|
||||
char *temp_ptr = line_ptr+strlen(line_ptr)-1;
|
||||
while ( *temp_ptr == ' ' || *temp_ptr == '\t' )
|
||||
{
|
||||
*temp_ptr-- = '\0';
|
||||
temp_ptr--;
|
||||
}
|
||||
// Remove trailing white space
|
||||
char *temp_ptr = line_ptr+strlen(line_ptr)-1;
|
||||
while ( *temp_ptr == ' ' || *temp_ptr == '\t' )
|
||||
{
|
||||
*temp_ptr-- = '\0';
|
||||
temp_ptr--;
|
||||
}
|
||||
|
||||
// Now look for the '=' in the middle of the line
|
||||
temp_ptr = strchr( line_ptr, '=' );
|
||||
if ( !temp_ptr )
|
||||
{
|
||||
Warning( "Invalid data in %s: '%s'", ZM_CONFIG, line );
|
||||
continue;
|
||||
}
|
||||
// Now look for the '=' in the middle of the line
|
||||
temp_ptr = strchr( line_ptr, '=' );
|
||||
if ( !temp_ptr )
|
||||
{
|
||||
Warning( "Invalid data in %s: '%s'", ZM_CONFIG, line );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assign the name and value parts
|
||||
char *name_ptr = line_ptr;
|
||||
char *val_ptr = temp_ptr+1;
|
||||
// Assign the name and value parts
|
||||
char *name_ptr = line_ptr;
|
||||
char *val_ptr = temp_ptr+1;
|
||||
|
||||
// Trim trailing space from the name part
|
||||
do
|
||||
{
|
||||
*temp_ptr = '\0';
|
||||
temp_ptr--;
|
||||
}
|
||||
while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
|
||||
// Trim trailing space from the name part
|
||||
do
|
||||
{
|
||||
*temp_ptr = '\0';
|
||||
temp_ptr--;
|
||||
}
|
||||
while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
|
||||
|
||||
// Remove leading white space from the value part
|
||||
white_len = strspn( val_ptr, " \t" );
|
||||
val_ptr += white_len;
|
||||
// Remove leading white space from the value part
|
||||
white_len = strspn( val_ptr, " \t" );
|
||||
val_ptr += white_len;
|
||||
|
||||
if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 )
|
||||
staticConfig.DB_HOST = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 )
|
||||
staticConfig.DB_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 )
|
||||
staticConfig.DB_USER = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 )
|
||||
staticConfig.DB_PASS = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 )
|
||||
staticConfig.PATH_WEB = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 )
|
||||
staticConfig.SERVER_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_NAME" ) == 0 )
|
||||
staticConfig.SERVER_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 )
|
||||
staticConfig.SERVER_ID = atoi(val_ptr);
|
||||
else
|
||||
{
|
||||
// We ignore this now as there may be more parameters than the
|
||||
// c/c++ binaries are bothered about
|
||||
// Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG );
|
||||
}
|
||||
} // end foreach line of the config
|
||||
fclose( cfg );
|
||||
zmDbConnect();
|
||||
config.Load();
|
||||
config.Assign();
|
||||
if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 )
|
||||
staticConfig.DB_HOST = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 )
|
||||
staticConfig.DB_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 )
|
||||
staticConfig.DB_USER = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 )
|
||||
staticConfig.DB_PASS = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 )
|
||||
staticConfig.PATH_WEB = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 )
|
||||
staticConfig.SERVER_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_NAME" ) == 0 )
|
||||
staticConfig.SERVER_NAME = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 )
|
||||
staticConfig.SERVER_ID = atoi(val_ptr);
|
||||
else
|
||||
{
|
||||
// We ignore this now as there may be more parameters than the
|
||||
// c/c++ binaries are bothered about
|
||||
// Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG );
|
||||
}
|
||||
} // end foreach line of the config
|
||||
fclose( cfg );
|
||||
zmDbConnect();
|
||||
config.Load();
|
||||
config.Assign();
|
||||
|
||||
// Populate the server config entries
|
||||
if ( ! staticConfig.SERVER_ID ) {
|
||||
if ( ! staticConfig.SERVER_NAME.empty() ) {
|
||||
// Populate the server config entries
|
||||
if ( ! staticConfig.SERVER_ID ) {
|
||||
if ( ! staticConfig.SERVER_NAME.empty() ) {
|
||||
|
||||
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() ) ) {
|
||||
staticConfig.SERVER_ID = atoi(dbrow[0]);
|
||||
} else {
|
||||
Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str() );
|
||||
}
|
||||
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() );
|
||||
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() );
|
||||
}
|
||||
|
||||
} // end if has SERVER_NAME
|
||||
} else if ( staticConfig.SERVER_NAME.empty() ) {
|
||||
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() ) ) {
|
||||
staticConfig.SERVER_NAME = std::string(dbrow[0]);
|
||||
} else {
|
||||
Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID );
|
||||
}
|
||||
|
||||
}
|
||||
if ( ! staticConfig.SERVER_ID ) {
|
||||
Debug( 1, "No Server ID or Name specified in config. Not using Multi-Server Mode." );
|
||||
} else {
|
||||
Debug( 1, "Server is %d: using Multi-Server Mode.", staticConfig.SERVER_ID );
|
||||
}
|
||||
} // end if has SERVER_NAME
|
||||
} else if ( staticConfig.SERVER_NAME.empty() ) {
|
||||
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 );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
}
|
||||
if ( ! staticConfig.SERVER_ID ) {
|
||||
Debug( 1, "No Server ID or Name specified in config. Not using Multi-Server Mode." );
|
||||
} else {
|
||||
Debug( 1, "Server is %d: using Multi-Server Mode.", staticConfig.SERVER_ID );
|
||||
}
|
||||
}
|
||||
|
||||
StaticConfig staticConfig;
|
||||
|
||||
ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type )
|
||||
{
|
||||
name = new char[strlen(p_name)+1];
|
||||
strcpy( name, p_name );
|
||||
value = new char[strlen(p_value)+1];
|
||||
strcpy( value, p_value );
|
||||
type = new char[strlen(p_type)+1];
|
||||
strcpy( type, p_type );
|
||||
name = new char[strlen(p_name)+1];
|
||||
strcpy( name, p_name );
|
||||
value = new char[strlen(p_value)+1];
|
||||
strcpy( value, p_value );
|
||||
type = new char[strlen(p_type)+1];
|
||||
strcpy( type, p_type );
|
||||
|
||||
//Info( "Created new config item %s = %s (%s)\n", name, value, type );
|
||||
//Info( "Created new config item %s = %s (%s)\n", name, value, type );
|
||||
|
||||
accessed = false;
|
||||
accessed = false;
|
||||
}
|
||||
|
||||
ConfigItem::~ConfigItem()
|
||||
{
|
||||
delete[] name;
|
||||
delete[] value;
|
||||
delete[] type;
|
||||
delete[] name;
|
||||
delete[] value;
|
||||
delete[] type;
|
||||
}
|
||||
|
||||
void ConfigItem::ConvertValue() const
|
||||
{
|
||||
if ( !strcmp( type, "boolean" ) )
|
||||
{
|
||||
cfg_type = CFG_BOOLEAN;
|
||||
cfg_value.boolean_value = (bool)strtol( value, 0, 0 );
|
||||
}
|
||||
else if ( !strcmp( type, "integer" ) )
|
||||
{
|
||||
cfg_type = CFG_INTEGER;
|
||||
cfg_value.integer_value = strtol( value, 0, 10 );
|
||||
}
|
||||
else if ( !strcmp( type, "hexadecimal" ) )
|
||||
{
|
||||
cfg_type = CFG_INTEGER;
|
||||
cfg_value.integer_value = strtol( value, 0, 16 );
|
||||
}
|
||||
else if ( !strcmp( type, "decimal" ) )
|
||||
{
|
||||
cfg_type = CFG_DECIMAL;
|
||||
cfg_value.decimal_value = strtod( value, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
cfg_type = CFG_STRING;
|
||||
cfg_value.string_value = value;
|
||||
}
|
||||
accessed = true;
|
||||
if ( !strcmp( type, "boolean" ) )
|
||||
{
|
||||
cfg_type = CFG_BOOLEAN;
|
||||
cfg_value.boolean_value = (bool)strtol( value, 0, 0 );
|
||||
}
|
||||
else if ( !strcmp( type, "integer" ) )
|
||||
{
|
||||
cfg_type = CFG_INTEGER;
|
||||
cfg_value.integer_value = strtol( value, 0, 10 );
|
||||
}
|
||||
else if ( !strcmp( type, "hexadecimal" ) )
|
||||
{
|
||||
cfg_type = CFG_INTEGER;
|
||||
cfg_value.integer_value = strtol( value, 0, 16 );
|
||||
}
|
||||
else if ( !strcmp( type, "decimal" ) )
|
||||
{
|
||||
cfg_type = CFG_DECIMAL;
|
||||
cfg_value.decimal_value = strtod( value, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
cfg_type = CFG_STRING;
|
||||
cfg_value.string_value = value;
|
||||
}
|
||||
accessed = true;
|
||||
}
|
||||
|
||||
bool ConfigItem::BooleanValue() const
|
||||
{
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
|
||||
if ( cfg_type != CFG_BOOLEAN )
|
||||
{
|
||||
Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( cfg_type != CFG_BOOLEAN )
|
||||
{
|
||||
Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( cfg_value.boolean_value );
|
||||
return( cfg_value.boolean_value );
|
||||
}
|
||||
|
||||
int ConfigItem::IntegerValue() const
|
||||
{
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
|
||||
if ( cfg_type != CFG_INTEGER )
|
||||
{
|
||||
Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( cfg_type != CFG_INTEGER )
|
||||
{
|
||||
Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( cfg_value.integer_value );
|
||||
return( cfg_value.integer_value );
|
||||
}
|
||||
|
||||
double ConfigItem::DecimalValue() const
|
||||
{
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
|
||||
if ( cfg_type != CFG_DECIMAL )
|
||||
{
|
||||
Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( cfg_type != CFG_DECIMAL )
|
||||
{
|
||||
Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( cfg_value.decimal_value );
|
||||
return( cfg_value.decimal_value );
|
||||
}
|
||||
|
||||
const char *ConfigItem::StringValue() const
|
||||
{
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
if ( !accessed )
|
||||
ConvertValue();
|
||||
|
||||
if ( cfg_type != CFG_STRING )
|
||||
{
|
||||
Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( cfg_type != CFG_STRING )
|
||||
{
|
||||
Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( cfg_value.string_value );
|
||||
return( cfg_value.string_value );
|
||||
}
|
||||
|
||||
Config::Config()
|
||||
{
|
||||
n_items = 0;
|
||||
items = 0;
|
||||
n_items = 0;
|
||||
items = 0;
|
||||
}
|
||||
|
||||
Config::~Config()
|
||||
{
|
||||
if ( items )
|
||||
{
|
||||
for ( int i = 0; i < n_items; i++ )
|
||||
{
|
||||
delete items[i];
|
||||
}
|
||||
delete[] items;
|
||||
}
|
||||
if ( items )
|
||||
{
|
||||
for ( int i = 0; i < n_items; i++ )
|
||||
{
|
||||
delete items[i];
|
||||
}
|
||||
delete[] items;
|
||||
}
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
{
|
||||
static char sql[ZM_SQL_SML_BUFSIZ];
|
||||
static char sql[ZM_SQL_SML_BUFSIZ];
|
||||
|
||||
strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(sql) );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||
exit( mysql_errno( &dbconn ) );
|
||||
}
|
||||
strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(sql) );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||
exit( mysql_errno( &dbconn ) );
|
||||
}
|
||||
|
||||
MYSQL_RES *result = mysql_store_result( &dbconn );
|
||||
if ( !result )
|
||||
{
|
||||
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
||||
exit( mysql_errno( &dbconn ) );
|
||||
}
|
||||
n_items = mysql_num_rows( result );
|
||||
MYSQL_RES *result = mysql_store_result( &dbconn );
|
||||
if ( !result )
|
||||
{
|
||||
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
||||
exit( mysql_errno( &dbconn ) );
|
||||
}
|
||||
n_items = mysql_num_rows( result );
|
||||
|
||||
if ( n_items <= ZM_MAX_CFG_ID )
|
||||
{
|
||||
Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( n_items <= ZM_MAX_CFG_ID )
|
||||
{
|
||||
Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
items = new ConfigItem *[n_items];
|
||||
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
|
||||
{
|
||||
items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] );
|
||||
}
|
||||
mysql_free_result( result );
|
||||
items = new ConfigItem *[n_items];
|
||||
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ )
|
||||
{
|
||||
items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] );
|
||||
}
|
||||
mysql_free_result( result );
|
||||
}
|
||||
|
||||
void Config::Assign()
|
||||
|
@ -309,27 +311,27 @@ ZM_CFG_ASSIGN_LIST
|
|||
|
||||
const ConfigItem &Config::Item( int id )
|
||||
{
|
||||
if ( !n_items )
|
||||
{
|
||||
Load();
|
||||
Assign();
|
||||
}
|
||||
if ( !n_items )
|
||||
{
|
||||
Load();
|
||||
Assign();
|
||||
}
|
||||
|
||||
if ( id < 0 || id > ZM_MAX_CFG_ID )
|
||||
{
|
||||
Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( id < 0 || id > ZM_MAX_CFG_ID )
|
||||
{
|
||||
Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
ConfigItem *item = items[id];
|
||||
|
||||
if ( !item )
|
||||
{
|
||||
Error( "Can't find config item %d", id );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( *item );
|
||||
ConfigItem *item = items[id];
|
||||
|
||||
if ( !item )
|
||||
{
|
||||
Error( "Can't find config item %d", id );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
return( *item );
|
||||
}
|
||||
|
||||
Config config;
|
||||
|
|
|
@ -30,524 +30,532 @@ 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 ),
|
||||
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
|
||||
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 )
|
||||
{
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
}
|
||||
|
||||
cURLCamera::~cURLCamera()
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
|
||||
Terminate();
|
||||
}
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void cURLCamera::Initialise()
|
||||
{
|
||||
content_length_match_len = strlen(content_length_match);
|
||||
content_type_match_len = strlen(content_type_match);
|
||||
content_length_match_len = strlen(content_length_match);
|
||||
content_type_match_len = strlen(content_type_match);
|
||||
|
||||
databuffer.expand(CURL_BUFFER_INITIAL_SIZE);
|
||||
databuffer.expand(CURL_BUFFER_INITIAL_SIZE);
|
||||
|
||||
/* cURL initialization */
|
||||
cRet = curl_global_init(CURL_GLOBAL_ALL);
|
||||
if(cRet != CURLE_OK) {
|
||||
Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet));
|
||||
}
|
||||
/* cURL initialization */
|
||||
cRet = curl_global_init(CURL_GLOBAL_ALL);
|
||||
if(cRet != CURLE_OK) {
|
||||
Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet));
|
||||
}
|
||||
|
||||
Debug(2,"libcurl version: %s",curl_version());
|
||||
Debug(2,"libcurl version: %s",curl_version());
|
||||
|
||||
/* Create the shared data mutex */
|
||||
nRet = pthread_mutex_init(&shareddata_mutex, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Shared data mutex creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the data available condition variable */
|
||||
nRet = pthread_cond_init(&data_available_cond, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Data available condition variable creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the request complete condition variable */
|
||||
nRet = pthread_cond_init(&request_complete_cond, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Request complete condition variable creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the shared data mutex */
|
||||
nRet = pthread_mutex_init(&shareddata_mutex, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Shared data mutex creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the data available condition variable */
|
||||
nRet = pthread_cond_init(&data_available_cond, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Data available condition variable creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the request complete condition variable */
|
||||
nRet = pthread_cond_init(&request_complete_cond, NULL);
|
||||
if(nRet != 0) {
|
||||
Fatal("Request complete condition variable creation failed: %s",strerror(nRet));
|
||||
}
|
||||
|
||||
/* Create the thread */
|
||||
nRet = pthread_create(&thread, NULL, thread_func_dispatcher, this);
|
||||
if(nRet != 0) {
|
||||
Fatal("Thread creation failed: %s",strerror(nRet));
|
||||
}
|
||||
/* Create the thread */
|
||||
nRet = pthread_create(&thread, NULL, thread_func_dispatcher, this);
|
||||
if(nRet != 0) {
|
||||
Fatal("Thread creation failed: %s",strerror(nRet));
|
||||
}
|
||||
}
|
||||
|
||||
void cURLCamera::Terminate()
|
||||
{
|
||||
/* Signal the thread to terminate */
|
||||
bTerminate = true;
|
||||
/* Signal the thread to terminate */
|
||||
bTerminate = true;
|
||||
|
||||
/* Wait for thread termination */
|
||||
pthread_join(thread, NULL);
|
||||
/* Wait for thread termination */
|
||||
pthread_join(thread, NULL);
|
||||
|
||||
/* Destroy condition variables */
|
||||
pthread_cond_destroy(&request_complete_cond);
|
||||
pthread_cond_destroy(&data_available_cond);
|
||||
/* Destroy condition variables */
|
||||
pthread_cond_destroy(&request_complete_cond);
|
||||
pthread_cond_destroy(&data_available_cond);
|
||||
|
||||
/* Destroy mutex */
|
||||
pthread_mutex_destroy(&shareddata_mutex);
|
||||
/* Destroy mutex */
|
||||
pthread_mutex_destroy(&shareddata_mutex);
|
||||
|
||||
/* cURL cleanup */
|
||||
curl_global_cleanup();
|
||||
/* cURL cleanup */
|
||||
curl_global_cleanup();
|
||||
|
||||
}
|
||||
|
||||
int cURLCamera::PrimeCapture()
|
||||
{
|
||||
//Info( "Priming capture from %s", mPath.c_str() );
|
||||
return 0;
|
||||
//Info( "Priming capture from %s", mPath.c_str() );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cURLCamera::PreCapture()
|
||||
{
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int cURLCamera::Capture( Image &image )
|
||||
{
|
||||
bool frameComplete = false;
|
||||
bool frameComplete = false;
|
||||
|
||||
/* MODE_STREAM specific variables */
|
||||
bool SubHeadersParsingComplete = false;
|
||||
unsigned int frame_content_length = 0;
|
||||
std::string frame_content_type;
|
||||
bool need_more_data = false;
|
||||
/* MODE_STREAM specific variables */
|
||||
bool SubHeadersParsingComplete = false;
|
||||
unsigned int frame_content_length = 0;
|
||||
std::string frame_content_type;
|
||||
bool need_more_data = false;
|
||||
|
||||
/* Grab the mutex to ensure exclusive access to the shared data */
|
||||
lock();
|
||||
/* Grab the mutex to ensure exclusive access to the shared data */
|
||||
lock();
|
||||
|
||||
while (!frameComplete) {
|
||||
while (!frameComplete) {
|
||||
|
||||
/* If the work thread did a reset, reset our local variables */
|
||||
if(bReset) {
|
||||
SubHeadersParsingComplete = false;
|
||||
frame_content_length = 0;
|
||||
frame_content_type.clear();
|
||||
need_more_data = false;
|
||||
bReset = false;
|
||||
}
|
||||
/* If the work thread did a reset, reset our local variables */
|
||||
if(bReset) {
|
||||
SubHeadersParsingComplete = false;
|
||||
frame_content_length = 0;
|
||||
frame_content_type.clear();
|
||||
need_more_data = false;
|
||||
bReset = false;
|
||||
}
|
||||
|
||||
if(mode == MODE_UNSET) {
|
||||
/* Don't have a mode yet. Sleep while waiting for data */
|
||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
||||
return -20;
|
||||
}
|
||||
}
|
||||
if(mode == MODE_UNSET) {
|
||||
/* Don't have a mode yet. Sleep while waiting for data */
|
||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
||||
return -20;
|
||||
}
|
||||
}
|
||||
|
||||
if(mode == MODE_STREAM) {
|
||||
if(mode == MODE_STREAM) {
|
||||
|
||||
/* Subheader parsing */
|
||||
while(!SubHeadersParsingComplete && !need_more_data) {
|
||||
/* Subheader parsing */
|
||||
while(!SubHeadersParsingComplete && !need_more_data) {
|
||||
|
||||
size_t crlf_start, crlf_end, crlf_size;
|
||||
std::string subheader;
|
||||
size_t crlf_start, crlf_end, crlf_size;
|
||||
std::string subheader;
|
||||
|
||||
/* Check if the buffer contains something */
|
||||
if(databuffer.empty()) {
|
||||
/* Empty buffer, wait for data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Find crlf start */
|
||||
crlf_start = memcspn(databuffer,"\r\n",databuffer.size());
|
||||
if(crlf_start == databuffer.size()) {
|
||||
/* Not found, wait for more data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
/* Check if the buffer contains something */
|
||||
if(databuffer.empty()) {
|
||||
/* Empty buffer, wait for data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Find crlf start */
|
||||
crlf_start = memcspn(databuffer,"\r\n",databuffer.size());
|
||||
if(crlf_start == databuffer.size()) {
|
||||
/* Not found, wait for more data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* See if we have enough data for determining crlf length */
|
||||
if(databuffer.size() < crlf_start+5) {
|
||||
/* Need more data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
/* See if we have enough data for determining crlf length */
|
||||
if(databuffer.size() < crlf_start+5) {
|
||||
/* Need more data */
|
||||
need_more_data = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Find crlf end and calculate crlf size */
|
||||
crlf_end = memspn(((const char*)databuffer.head())+crlf_start,"\r\n",5);
|
||||
crlf_size = (crlf_start + crlf_end) - crlf_start;
|
||||
/* Find crlf end and calculate crlf size */
|
||||
crlf_end = memspn(((const char*)databuffer.head())+crlf_start,"\r\n",5);
|
||||
crlf_size = (crlf_start + crlf_end) - crlf_start;
|
||||
|
||||
/* Is this the end of a previous stream? (This is just before the boundary) */
|
||||
if(crlf_start == 0) {
|
||||
databuffer.consume(crlf_size);
|
||||
continue;
|
||||
}
|
||||
/* Is this the end of a previous stream? (This is just before the boundary) */
|
||||
if(crlf_start == 0) {
|
||||
databuffer.consume(crlf_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for invalid CRLF size */
|
||||
if(crlf_size > 4) {
|
||||
Error("Invalid CRLF length");
|
||||
}
|
||||
/* Check for invalid CRLF size */
|
||||
if(crlf_size > 4) {
|
||||
Error("Invalid CRLF length");
|
||||
}
|
||||
|
||||
/* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */
|
||||
if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) {
|
||||
/* This is the last header */
|
||||
SubHeadersParsingComplete = true;
|
||||
}
|
||||
/* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */
|
||||
if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) {
|
||||
/* This is the last header */
|
||||
SubHeadersParsingComplete = true;
|
||||
}
|
||||
|
||||
/* Copy the subheader, excluding the crlf */
|
||||
subheader.assign(databuffer, crlf_start);
|
||||
/* Copy the subheader, excluding the crlf */
|
||||
subheader.assign(databuffer, crlf_start);
|
||||
|
||||
/* Advance the buffer past this one */
|
||||
databuffer.consume(crlf_start+crlf_size);
|
||||
/* Advance the buffer past this one */
|
||||
databuffer.consume(crlf_start+crlf_size);
|
||||
|
||||
Debug(7,"Got subheader: %s",subheader.c_str());
|
||||
Debug(7,"Got subheader: %s",subheader.c_str());
|
||||
|
||||
/* Find where the data in this header starts */
|
||||
size_t subheader_data_start = subheader.rfind(' ');
|
||||
if(subheader_data_start == std::string::npos) {
|
||||
subheader_data_start = subheader.find(':');
|
||||
}
|
||||
/* Find where the data in this header starts */
|
||||
size_t subheader_data_start = subheader.rfind(' ');
|
||||
if(subheader_data_start == std::string::npos) {
|
||||
subheader_data_start = subheader.find(':');
|
||||
}
|
||||
|
||||
/* Extract the data into a string */
|
||||
std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos);
|
||||
/* Extract the data into a string */
|
||||
std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos);
|
||||
|
||||
Debug(8,"Got subheader data: %s",subheader_data.c_str());
|
||||
Debug(8,"Got subheader data: %s",subheader_data.c_str());
|
||||
|
||||
/* Check the header */
|
||||
if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) {
|
||||
/* Found the content-length header */
|
||||
frame_content_length = atoi(subheader_data.c_str());
|
||||
Debug(6,"Got content-length subheader: %d",frame_content_length);
|
||||
} else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) {
|
||||
/* Found the content-type header */
|
||||
frame_content_type = subheader_data;
|
||||
Debug(6,"Got content-type subheader: %s",frame_content_type.c_str());
|
||||
}
|
||||
/* Check the header */
|
||||
if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) {
|
||||
/* Found the content-length header */
|
||||
frame_content_length = atoi(subheader_data.c_str());
|
||||
Debug(6,"Got content-length subheader: %d",frame_content_length);
|
||||
} else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) {
|
||||
/* Found the content-type header */
|
||||
frame_content_type = subheader_data;
|
||||
Debug(6,"Got content-type subheader: %s",frame_content_type.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Attempt to extract the frame */
|
||||
if(!need_more_data) {
|
||||
if(!SubHeadersParsingComplete) {
|
||||
/* We haven't parsed all headers yet */
|
||||
need_more_data = true;
|
||||
} else if(frame_content_length <= 0) {
|
||||
/* Invalid frame */
|
||||
Error("Invalid frame: invalid content length");
|
||||
} else if(frame_content_type != "image/jpeg") {
|
||||
/* Unsupported frame type */
|
||||
Error("Unsupported frame: %s",frame_content_type.c_str());
|
||||
} else if(frame_content_length > databuffer.size()) {
|
||||
/* Incomplete frame, wait for more data */
|
||||
need_more_data = true;
|
||||
} else {
|
||||
/* All good. decode the image */
|
||||
image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
|
||||
frameComplete = true;
|
||||
}
|
||||
}
|
||||
/* Attempt to extract the frame */
|
||||
if(!need_more_data) {
|
||||
if(!SubHeadersParsingComplete) {
|
||||
/* We haven't parsed all headers yet */
|
||||
need_more_data = true;
|
||||
} else if(frame_content_length <= 0) {
|
||||
/* Invalid frame */
|
||||
Error("Invalid frame: invalid content length");
|
||||
} else if(frame_content_type != "image/jpeg") {
|
||||
/* Unsupported frame type */
|
||||
Error("Unsupported frame: %s",frame_content_type.c_str());
|
||||
} else if(frame_content_length > databuffer.size()) {
|
||||
/* Incomplete frame, wait for more data */
|
||||
need_more_data = true;
|
||||
} else {
|
||||
/* All good. decode the image */
|
||||
image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
|
||||
frameComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Attempt to get more data */
|
||||
if(need_more_data) {
|
||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
||||
return -18;
|
||||
}
|
||||
need_more_data = false;
|
||||
}
|
||||
/* Attempt to get more data */
|
||||
if(need_more_data) {
|
||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
||||
return -18;
|
||||
}
|
||||
need_more_data = false;
|
||||
}
|
||||
|
||||
} else if(mode == MODE_SINGLE) {
|
||||
/* Check if we have anything */
|
||||
if (!single_offsets.empty()) {
|
||||
if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) {
|
||||
/* Extract frame */
|
||||
image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
|
||||
single_offsets.pop_front();
|
||||
frameComplete = true;
|
||||
} else {
|
||||
/* This shouldn't happen */
|
||||
Error("Internal error. Attempting recovery");
|
||||
databuffer.consume(single_offsets.front());
|
||||
single_offsets.pop_front();
|
||||
}
|
||||
} else {
|
||||
/* Don't have a frame yet, wait for the request complete condition variable */
|
||||
nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for request complete condition variable: %s",strerror(nRet));
|
||||
return -19;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Failed to match content-type */
|
||||
Fatal("Unable to match Content-Type. Check URL, username and password");
|
||||
} /* mode */
|
||||
} else if(mode == MODE_SINGLE) {
|
||||
/* Check if we have anything */
|
||||
if (!single_offsets.empty()) {
|
||||
if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) {
|
||||
/* Extract frame */
|
||||
image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
|
||||
single_offsets.pop_front();
|
||||
frameComplete = true;
|
||||
} else {
|
||||
/* This shouldn't happen */
|
||||
Error("Internal error. Attempting recovery");
|
||||
databuffer.consume(single_offsets.front());
|
||||
single_offsets.pop_front();
|
||||
}
|
||||
} else {
|
||||
/* Don't have a frame yet, wait for the request complete condition variable */
|
||||
nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed waiting for request complete condition variable: %s",strerror(nRet));
|
||||
return -19;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Failed to match content-type */
|
||||
Fatal("Unable to match Content-Type. Check URL, username and password");
|
||||
} /* mode */
|
||||
|
||||
} /* frameComplete loop */
|
||||
} /* frameComplete loop */
|
||||
|
||||
/* Release the mutex */
|
||||
unlock();
|
||||
/* Release the mutex */
|
||||
unlock();
|
||||
|
||||
if(!frameComplete)
|
||||
return -1;
|
||||
if(!frameComplete)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cURLCamera::PostCapture()
|
||||
{
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
// Nothing to do here
|
||||
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();
|
||||
lock();
|
||||
|
||||
/* Append the data we just received to our buffer */
|
||||
databuffer.append((const char*)buffer, size*nmemb);
|
||||
/* Append the data we just received to our buffer */
|
||||
databuffer.append((const char*)buffer, size*nmemb);
|
||||
|
||||
/* Signal data available */
|
||||
nRet = pthread_cond_signal(&data_available_cond);
|
||||
if(nRet != 0) {
|
||||
Error("Failed signaling data available condition variable: %s",strerror(nRet));
|
||||
return -16;
|
||||
}
|
||||
/* Signal data available */
|
||||
nRet = pthread_cond_signal(&data_available_cond);
|
||||
if(nRet != 0) {
|
||||
Error("Failed signaling data available condition variable: %s",strerror(nRet));
|
||||
return -16;
|
||||
}
|
||||
|
||||
unlock();
|
||||
unlock();
|
||||
|
||||
/* Return bytes processed */
|
||||
return size*nmemb;
|
||||
/* Return bytes processed */
|
||||
return size*nmemb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
std::string header;
|
||||
header.assign((const char*)buffer, size*nmemb);
|
||||
|
||||
Debug(4,"Got header: %s",header.c_str());
|
||||
std::string header;
|
||||
header.assign((const char*)buffer, size*nmemb);
|
||||
|
||||
Debug(4,"Got header: %s",header.c_str());
|
||||
|
||||
/* Check Content-Type header */
|
||||
if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
|
||||
size_t pos = header.find(';');
|
||||
if(pos != std::string::npos) {
|
||||
header.erase(pos, std::string::npos);
|
||||
}
|
||||
/* Check Content-Type header */
|
||||
if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
|
||||
size_t pos = header.find(';');
|
||||
if(pos != std::string::npos) {
|
||||
header.erase(pos, std::string::npos);
|
||||
}
|
||||
|
||||
pos = header.rfind(' ');
|
||||
if(pos == std::string::npos) {
|
||||
pos = header.find(':');
|
||||
}
|
||||
pos = header.rfind(' ');
|
||||
if(pos == std::string::npos) {
|
||||
pos = header.find(':');
|
||||
}
|
||||
|
||||
std::string content_type = header.substr(pos+1, std::string::npos);
|
||||
Debug(6,"Content-Type is: %s",content_type.c_str());
|
||||
std::string content_type = header.substr(pos+1, std::string::npos);
|
||||
Debug(6,"Content-Type is: %s",content_type.c_str());
|
||||
|
||||
lock();
|
||||
lock();
|
||||
|
||||
const char* multipart_match = "multipart/x-mixed-replace";
|
||||
const char* image_jpeg_match = "image/jpeg";
|
||||
if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
|
||||
Debug(7,"Content type matched as multipart/x-mixed-replace");
|
||||
mode = MODE_STREAM;
|
||||
} else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
|
||||
Debug(7,"Content type matched as image/jpeg");
|
||||
mode = MODE_SINGLE;
|
||||
}
|
||||
const char* multipart_match = "multipart/x-mixed-replace";
|
||||
const char* image_jpeg_match = "image/jpeg";
|
||||
if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
|
||||
Debug(7,"Content type matched as multipart/x-mixed-replace");
|
||||
mode = MODE_STREAM;
|
||||
} else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
|
||||
Debug(7,"Content type matched as image/jpeg");
|
||||
mode = MODE_SINGLE;
|
||||
}
|
||||
|
||||
unlock();
|
||||
}
|
||||
|
||||
/* Return bytes processed */
|
||||
return size*nmemb;
|
||||
unlock();
|
||||
}
|
||||
|
||||
/* Return bytes processed */
|
||||
return size*nmemb;
|
||||
}
|
||||
|
||||
void* cURLCamera::thread_func()
|
||||
{
|
||||
long tRet;
|
||||
double dSize;
|
||||
long tRet;
|
||||
double dSize;
|
||||
|
||||
c = curl_easy_init();
|
||||
if(c == NULL) {
|
||||
Fatal("Failed getting easy handle from libcurl");
|
||||
}
|
||||
c = curl_easy_init();
|
||||
if(c == NULL) {
|
||||
Fatal("Failed getting easy handle from libcurl");
|
||||
}
|
||||
|
||||
/* Set URL */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl URL: %s", curl_easy_strerror(cRet));
|
||||
|
||||
/* Header callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl header callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HEADERDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl header callback object: %s", curl_easy_strerror(cRet));
|
||||
/* Set URL */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl URL: %s", curl_easy_strerror(cRet));
|
||||
|
||||
/* Header callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl header callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HEADERDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl header callback object: %s", curl_easy_strerror(cRet));
|
||||
|
||||
/* Data callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl data callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_WRITEDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl data callback object: %s", curl_easy_strerror(cRet));
|
||||
/* Data callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl data callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_WRITEDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl data callback object: %s", curl_easy_strerror(cRet));
|
||||
|
||||
/* Progress callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed enabling libcurl progress callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl progress callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl progress callback object: %s", curl_easy_strerror(cRet));
|
||||
/* Progress callback */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed enabling libcurl progress callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl progress callback function: %s", curl_easy_strerror(cRet));
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSDATA, this);
|
||||
if(cRet != CURLE_OK)
|
||||
Fatal("Failed setting libcurl progress callback object: %s", curl_easy_strerror(cRet));
|
||||
|
||||
/* Set username and password */
|
||||
if(!mUser.empty()) {
|
||||
cRet = curl_easy_setopt(c, CURLOPT_USERNAME, mUser.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Error("Failed setting username: %s", curl_easy_strerror(cRet));
|
||||
}
|
||||
if(!mPass.empty()) {
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PASSWORD, mPass.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Error("Failed setting password: %s", curl_easy_strerror(cRet));
|
||||
}
|
||||
/* Set username and password */
|
||||
if(!mUser.empty()) {
|
||||
cRet = curl_easy_setopt(c, CURLOPT_USERNAME, mUser.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Error("Failed setting username: %s", curl_easy_strerror(cRet));
|
||||
}
|
||||
if(!mPass.empty()) {
|
||||
cRet = curl_easy_setopt(c, CURLOPT_PASSWORD, mPass.c_str());
|
||||
if(cRet != CURLE_OK)
|
||||
Error("Failed setting password: %s", curl_easy_strerror(cRet));
|
||||
}
|
||||
|
||||
/* Authenication preference */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
if(cRet != CURLE_OK)
|
||||
Warning("Failed setting libcurl acceptable http authenication methods: %s", curl_easy_strerror(cRet));
|
||||
/* Authenication preference */
|
||||
cRet = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
if(cRet != CURLE_OK)
|
||||
Warning("Failed setting libcurl acceptable http authenication methods: %s", curl_easy_strerror(cRet));
|
||||
|
||||
|
||||
/* Work loop */
|
||||
for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
|
||||
tRet = 0;
|
||||
while(!bTerminate) {
|
||||
/* Do the work */
|
||||
cRet = curl_easy_perform(c);
|
||||
/* Work loop */
|
||||
for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
|
||||
tRet = 0;
|
||||
while(!bTerminate) {
|
||||
/* Do the work */
|
||||
cRet = curl_easy_perform(c);
|
||||
|
||||
if(mode == MODE_SINGLE) {
|
||||
if(cRet != CURLE_OK) {
|
||||
break;
|
||||
}
|
||||
/* Attempt to get the size of the file */
|
||||
cRet = curl_easy_getinfo(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
|
||||
if(cRet != CURLE_OK) {
|
||||
break;
|
||||
}
|
||||
/* We need to lock for the offsets array and the condition variable */
|
||||
lock();
|
||||
/* Push the size into our offsets array */
|
||||
if(dSize > 0) {
|
||||
single_offsets.push_back(dSize);
|
||||
} else {
|
||||
Fatal("Unable to get the size of the image");
|
||||
}
|
||||
/* Signal the request complete condition variable */
|
||||
tRet = pthread_cond_signal(&request_complete_cond);
|
||||
if(tRet != 0) {
|
||||
Error("Failed signaling request completed condition variable: %s",strerror(tRet));
|
||||
}
|
||||
/* Unlock */
|
||||
unlock();
|
||||
if(mode == MODE_SINGLE) {
|
||||
if(cRet != CURLE_OK) {
|
||||
break;
|
||||
}
|
||||
/* Attempt to get the size of the file */
|
||||
cRet = curl_easy_getinfo(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
|
||||
if(cRet != CURLE_OK) {
|
||||
break;
|
||||
}
|
||||
/* We need to lock for the offsets array and the condition variable */
|
||||
lock();
|
||||
/* Push the size into our offsets array */
|
||||
if(dSize > 0) {
|
||||
single_offsets.push_back(dSize);
|
||||
} else {
|
||||
Fatal("Unable to get the size of the image");
|
||||
}
|
||||
/* Signal the request complete condition variable */
|
||||
tRet = pthread_cond_signal(&request_complete_cond);
|
||||
if(tRet != 0) {
|
||||
Error("Failed signaling request completed condition variable: %s",strerror(tRet));
|
||||
}
|
||||
/* Unlock */
|
||||
unlock();
|
||||
|
||||
} else if (mode == MODE_STREAM) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (mode == MODE_STREAM) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return value checking */
|
||||
if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
|
||||
/* Aborted */
|
||||
break;
|
||||
} else if (cRet != CURLE_OK) {
|
||||
/* Some error */
|
||||
Error("cURL Request failed: %s",curl_easy_strerror(cRet));
|
||||
if(attempt < CURL_MAXRETRY) {
|
||||
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
|
||||
/* Do a reset */
|
||||
lock();
|
||||
databuffer.clear();
|
||||
single_offsets.clear();
|
||||
mode = MODE_UNSET;
|
||||
bReset = true;
|
||||
unlock();
|
||||
}
|
||||
tRet = -50;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
curl_easy_cleanup(c);
|
||||
c = NULL;
|
||||
|
||||
return (void*)tRet;
|
||||
/* Return value checking */
|
||||
if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
|
||||
/* Aborted */
|
||||
break;
|
||||
} else if (cRet != CURLE_OK) {
|
||||
/* Some error */
|
||||
Error("cURL Request failed: %s",curl_easy_strerror(cRet));
|
||||
if(attempt < CURL_MAXRETRY) {
|
||||
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
|
||||
/* Do a reset */
|
||||
lock();
|
||||
databuffer.clear();
|
||||
single_offsets.clear();
|
||||
mode = MODE_UNSET;
|
||||
bReset = true;
|
||||
unlock();
|
||||
}
|
||||
tRet = -50;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
curl_easy_cleanup(c);
|
||||
c = NULL;
|
||||
|
||||
return (void*)tRet;
|
||||
}
|
||||
|
||||
int cURLCamera::lock() {
|
||||
int nRet;
|
||||
int nRet;
|
||||
|
||||
/* Lock shared data */
|
||||
nRet = pthread_mutex_lock(&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed locking shared data mutex: %s",strerror(nRet));
|
||||
}
|
||||
return nRet;
|
||||
/* Lock shared data */
|
||||
nRet = pthread_mutex_lock(&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed locking shared data mutex: %s",strerror(nRet));
|
||||
}
|
||||
return nRet;
|
||||
}
|
||||
|
||||
int cURLCamera::unlock() {
|
||||
int nRet;
|
||||
int nRet;
|
||||
|
||||
/* Unlock shared data */
|
||||
nRet = pthread_mutex_unlock(&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed unlocking shared data mutex: %s",strerror(nRet));
|
||||
}
|
||||
return nRet;
|
||||
/* Unlock shared data */
|
||||
nRet = pthread_mutex_unlock(&shareddata_mutex);
|
||||
if(nRet != 0) {
|
||||
Error("Failed unlocking shared data mutex: %s",strerror(nRet));
|
||||
}
|
||||
return nRet;
|
||||
}
|
||||
|
||||
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
/* Signal the curl thread to terminate */
|
||||
if(bTerminate)
|
||||
return -10;
|
||||
|
||||
return 0;
|
||||
/* Signal the curl thread to terminate */
|
||||
if(bTerminate)
|
||||
return -10;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* These functions call the functions in the class for the correct object */
|
||||
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata);
|
||||
return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata);
|
||||
}
|
||||
|
||||
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata);
|
||||
return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata);
|
||||
}
|
||||
|
||||
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
||||
return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
||||
}
|
||||
|
||||
void* thread_func_dispatcher(void* object) {
|
||||
return ((cURLCamera*)object)->thread_func();
|
||||
return ((cURLCamera*)object)->thread_func();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,55 +42,57 @@
|
|||
class cURLCamera : public Camera
|
||||
{
|
||||
protected:
|
||||
typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t;
|
||||
typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t;
|
||||
|
||||
std::string mPath;
|
||||
std::string mUser;
|
||||
std::string mPass;
|
||||
std::string mPath;
|
||||
std::string mUser;
|
||||
std::string mPass;
|
||||
|
||||
/* cURL object(s) */
|
||||
CURL* c;
|
||||
/* cURL object(s) */
|
||||
CURL* c;
|
||||
|
||||
/* Shared data */
|
||||
volatile bool bTerminate;
|
||||
volatile bool bReset;
|
||||
volatile mode_t mode;
|
||||
Buffer databuffer;
|
||||
std::deque<size_t> single_offsets;
|
||||
/* Shared data */
|
||||
volatile bool bTerminate;
|
||||
volatile bool bReset;
|
||||
volatile mode_t mode;
|
||||
Buffer databuffer;
|
||||
std::deque<size_t> single_offsets;
|
||||
|
||||
/* pthread objects */
|
||||
pthread_t thread;
|
||||
pthread_mutex_t shareddata_mutex;
|
||||
pthread_cond_t data_available_cond;
|
||||
pthread_cond_t request_complete_cond;
|
||||
/* pthread objects */
|
||||
pthread_t thread;
|
||||
pthread_mutex_t shareddata_mutex;
|
||||
pthread_cond_t data_available_cond;
|
||||
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();
|
||||
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 ); }
|
||||
const std::string &Username() const { return( mUser ); }
|
||||
const std::string &Password() const { return( mPass ); }
|
||||
const std::string &Path() const { return( mPath ); }
|
||||
const std::string &Username() const { return( mUser ); }
|
||||
const std::string &Password() const { return( mPass ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int PrimeCapture();
|
||||
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);
|
||||
int progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
int debug_callback(CURL* handle, curl_infotype type, char* str, size_t strsize, void* data);
|
||||
void* thread_func();
|
||||
int lock();
|
||||
int unlock();
|
||||
|
||||
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);
|
||||
int progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
int debug_callback(CURL* handle, curl_infotype type, char* str, size_t strsize, void* data);
|
||||
void* thread_func();
|
||||
int lock();
|
||||
int unlock();
|
||||
|
||||
private:
|
||||
int nRet;
|
||||
CURLcode cRet;
|
||||
int nRet;
|
||||
CURLcode cRet;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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" */
|
||||
|
|
2669
src/zm_event.cpp
2669
src/zm_event.cpp
File diff suppressed because it is too large
Load Diff
187
src/zm_event.h
187
src/zm_event.h
|
@ -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;
|
||||
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,104 +204,84 @@ public:
|
|||
|
||||
class EventStream : public StreamBase
|
||||
{
|
||||
public:
|
||||
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
||||
public:
|
||||
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
||||
|
||||
protected:
|
||||
<<<<<<< HEAD
|
||||
protected:
|
||||
struct FrameData {
|
||||
//unsigned long id;
|
||||
time_t timestamp;
|
||||
time_t offset;
|
||||
double delta;
|
||||
bool in_db;
|
||||
//unsigned long id;
|
||||
time_t timestamp;
|
||||
time_t offset;
|
||||
double delta;
|
||||
bool in_db;
|
||||
};
|
||||
|
||||
struct EventData
|
||||
{
|
||||
unsigned long event_id;
|
||||
unsigned long monitor_id;
|
||||
unsigned long storage_id;
|
||||
unsigned long frame_count;
|
||||
time_t start_time;
|
||||
double duration;
|
||||
char path[PATH_MAX];
|
||||
int n_frames;
|
||||
FrameData *frames;
|
||||
unsigned long event_id;
|
||||
unsigned long monitor_id;
|
||||
unsigned long storage_id;
|
||||
unsigned long frame_count;
|
||||
time_t start_time;
|
||||
double duration;
|
||||
char path[PATH_MAX];
|
||||
int n_frames;
|
||||
FrameData *frames;
|
||||
char video_file[PATH_MAX];
|
||||
};
|
||||
=======
|
||||
struct FrameData {
|
||||
//unsigned long id;
|
||||
time_t timestamp;
|
||||
time_t offset;
|
||||
double delta;
|
||||
bool in_db;
|
||||
};
|
||||
|
||||
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:
|
||||
static const int STREAM_PAUSE_WAIT = 250000; // Microseconds
|
||||
|
||||
protected:
|
||||
static const int STREAM_PAUSE_WAIT = 250000; // Microseconds
|
||||
static const StreamMode DEFAULT_MODE = MODE_SINGLE;
|
||||
|
||||
static const StreamMode DEFAULT_MODE = MODE_SINGLE;
|
||||
protected:
|
||||
StreamMode mode;
|
||||
bool forceEventChange;
|
||||
|
||||
protected:
|
||||
StreamMode mode;
|
||||
bool forceEventChange;
|
||||
protected:
|
||||
int curr_frame_id;
|
||||
double curr_stream_time;
|
||||
|
||||
protected:
|
||||
int curr_frame_id;
|
||||
double curr_stream_time;
|
||||
EventData *event_data;
|
||||
|
||||
EventData *event_data;
|
||||
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 );
|
||||
|
||||
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 );
|
||||
void checkEventLoaded();
|
||||
void processCommand( const CmdMsg *msg );
|
||||
bool sendFrame( int delta_us );
|
||||
|
||||
void checkEventLoaded();
|
||||
void processCommand( const CmdMsg *msg );
|
||||
bool sendFrame( int delta_us );
|
||||
public:
|
||||
EventStream()
|
||||
{
|
||||
mode = DEFAULT_MODE;
|
||||
|
||||
public:
|
||||
EventStream()
|
||||
{
|
||||
mode = DEFAULT_MODE;
|
||||
forceEventChange = false;
|
||||
|
||||
forceEventChange = false;
|
||||
curr_frame_id = 0;
|
||||
curr_stream_time = 0.0;
|
||||
|
||||
curr_frame_id = 0;
|
||||
curr_stream_time = 0.0;
|
||||
|
||||
event_data = 0;
|
||||
}
|
||||
void setStreamStart( int init_event_id, unsigned int init_frame_id=0 )
|
||||
{
|
||||
loadInitialEventData( init_event_id, init_frame_id );
|
||||
loadMonitor( event_data->monitor_id );
|
||||
}
|
||||
void setStreamStart( int monitor_id, time_t event_time )
|
||||
{
|
||||
loadInitialEventData( monitor_id, event_time );
|
||||
loadMonitor( monitor_id );
|
||||
}
|
||||
void setStreamMode( StreamMode p_mode )
|
||||
{
|
||||
mode = p_mode;
|
||||
}
|
||||
void runStream();
|
||||
event_data = 0;
|
||||
}
|
||||
void setStreamStart( int init_event_id, unsigned int init_frame_id=0 )
|
||||
{
|
||||
loadInitialEventData( init_event_id, init_frame_id );
|
||||
loadMonitor( event_data->monitor_id );
|
||||
}
|
||||
void setStreamStart( int monitor_id, time_t event_time )
|
||||
{
|
||||
loadInitialEventData( monitor_id, event_time );
|
||||
loadMonitor( monitor_id );
|
||||
}
|
||||
void setStreamMode( StreamMode p_mode )
|
||||
{
|
||||
mode = p_mode;
|
||||
}
|
||||
void runStream();
|
||||
Image *getImage();
|
||||
};
|
||||
|
||||
#endif // ZM_EVENT_H
|
||||
|
|
|
@ -23,213 +23,415 @@
|
|||
|
||||
#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;
|
||||
enum _AVPIXELFORMAT pf;
|
||||
|
||||
Debug(8,"Colours: %d SubpixelOrder: %d",p_colours,p_subpixelorder);
|
||||
Debug(8,"Colours: %d SubpixelOrder: %d",p_colours,p_subpixelorder);
|
||||
|
||||
switch(p_colours) {
|
||||
case ZM_COLOUR_RGB24:
|
||||
{
|
||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
|
||||
/* BGR subpixel order */
|
||||
pf = AV_PIX_FMT_BGR24;
|
||||
} else {
|
||||
/* Assume RGB subpixel order */
|
||||
pf = AV_PIX_FMT_RGB24;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZM_COLOUR_RGB32:
|
||||
{
|
||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
|
||||
/* ARGB subpixel order */
|
||||
pf = AV_PIX_FMT_ARGB;
|
||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
|
||||
/* ABGR subpixel order */
|
||||
pf = AV_PIX_FMT_ABGR;
|
||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
|
||||
/* BGRA subpixel order */
|
||||
pf = AV_PIX_FMT_BGRA;
|
||||
} else {
|
||||
/* Assume RGBA subpixel order */
|
||||
pf = AV_PIX_FMT_RGBA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZM_COLOUR_GRAY8:
|
||||
pf = AV_PIX_FMT_GRAY8;
|
||||
break;
|
||||
default:
|
||||
Panic("Unexpected colours: %d",p_colours);
|
||||
pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */
|
||||
break;
|
||||
}
|
||||
switch(p_colours) {
|
||||
case ZM_COLOUR_RGB24:
|
||||
{
|
||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
|
||||
/* BGR subpixel order */
|
||||
pf = AV_PIX_FMT_BGR24;
|
||||
} else {
|
||||
/* Assume RGB subpixel order */
|
||||
pf = AV_PIX_FMT_RGB24;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZM_COLOUR_RGB32:
|
||||
{
|
||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
|
||||
/* ARGB subpixel order */
|
||||
pf = AV_PIX_FMT_ARGB;
|
||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
|
||||
/* ABGR subpixel order */
|
||||
pf = AV_PIX_FMT_ABGR;
|
||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
|
||||
/* BGRA subpixel order */
|
||||
pf = AV_PIX_FMT_BGRA;
|
||||
} else {
|
||||
/* Assume RGBA subpixel order */
|
||||
pf = AV_PIX_FMT_RGBA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZM_COLOUR_GRAY8:
|
||||
pf = AV_PIX_FMT_GRAY8;
|
||||
break;
|
||||
default:
|
||||
Panic("Unexpected colours: %d",p_colours);
|
||||
pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */
|
||||
break;
|
||||
}
|
||||
|
||||
return pf;
|
||||
return pf;
|
||||
}
|
||||
#endif // HAVE_LIBAVUTIL
|
||||
|
||||
#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||
SWScale::SWScale() : gotdefaults(false), swscale_ctx(NULL), input_avframe(NULL), output_avframe(NULL) {
|
||||
Debug(4,"SWScale object created");
|
||||
Debug(4,"SWScale object created");
|
||||
|
||||
/* Allocate AVFrame for the input */
|
||||
/* Allocate AVFrame for the input */
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
input_avframe = av_frame_alloc();
|
||||
input_avframe = av_frame_alloc();
|
||||
#else
|
||||
input_avframe = avcodec_alloc_frame();
|
||||
input_avframe = avcodec_alloc_frame();
|
||||
#endif
|
||||
if(input_avframe == NULL) {
|
||||
Fatal("Failed allocating AVFrame for the input");
|
||||
}
|
||||
if(input_avframe == NULL) {
|
||||
Fatal("Failed allocating AVFrame for the input");
|
||||
}
|
||||
|
||||
/* Allocate AVFrame for the output */
|
||||
/* Allocate AVFrame for the output */
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
output_avframe = av_frame_alloc();
|
||||
output_avframe = av_frame_alloc();
|
||||
#else
|
||||
output_avframe = avcodec_alloc_frame();
|
||||
output_avframe = avcodec_alloc_frame();
|
||||
#endif
|
||||
if(output_avframe == NULL) {
|
||||
Fatal("Failed allocating AVFrame for the output");
|
||||
}
|
||||
if(output_avframe == NULL) {
|
||||
Fatal("Failed allocating AVFrame for the output");
|
||||
}
|
||||
}
|
||||
|
||||
SWScale::~SWScale() {
|
||||
|
||||
/* Free up everything */
|
||||
/* Free up everything */
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
av_frame_free( &input_avframe );
|
||||
av_frame_free( &input_avframe );
|
||||
#else
|
||||
av_freep( &input_avframe );
|
||||
av_freep( &input_avframe );
|
||||
#endif
|
||||
//input_avframe = NULL;
|
||||
//input_avframe = NULL;
|
||||
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
av_frame_free( &output_avframe );
|
||||
av_frame_free( &output_avframe );
|
||||
#else
|
||||
av_freep( &output_avframe );
|
||||
av_freep( &output_avframe );
|
||||
#endif
|
||||
//output_avframe = NULL;
|
||||
//output_avframe = NULL;
|
||||
|
||||
if(swscale_ctx) {
|
||||
sws_freeContext(swscale_ctx);
|
||||
swscale_ctx = NULL;
|
||||
}
|
||||
|
||||
Debug(4,"SWScale object destroyed");
|
||||
if(swscale_ctx) {
|
||||
sws_freeContext(swscale_ctx);
|
||||
swscale_ctx = NULL;
|
||||
}
|
||||
|
||||
Debug(4,"SWScale object destroyed");
|
||||
}
|
||||
|
||||
int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) {
|
||||
|
||||
/* Assign the defaults */
|
||||
default_input_pf = in_pf;
|
||||
default_output_pf = out_pf;
|
||||
default_width = width;
|
||||
default_height = height;
|
||||
/* Assign the defaults */
|
||||
default_input_pf = in_pf;
|
||||
default_output_pf = out_pf;
|
||||
default_width = width;
|
||||
default_height = height;
|
||||
|
||||
gotdefaults = true;
|
||||
gotdefaults = true;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) {
|
||||
/* Parameter checking */
|
||||
if(in_buffer == NULL || out_buffer == NULL) {
|
||||
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;
|
||||
}
|
||||
/* Parameter checking */
|
||||
if(in_buffer == NULL || out_buffer == NULL) {
|
||||
Error("NULL Input or output buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!width || !height) {
|
||||
Error("Invalid width or height");
|
||||
return -3;
|
||||
}
|
||||
|
||||
#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0)
|
||||
/* Warn if the input or output pixelformat is not supported */
|
||||
if(!sws_isSupportedInput(in_pf)) {
|
||||
Warning("swscale does not support the input format: %c%c%c%c",(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff));
|
||||
}
|
||||
if(!sws_isSupportedOutput(out_pf)) {
|
||||
Warning("swscale does not support the output format: %c%c%c%c",(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff));
|
||||
}
|
||||
/* Warn if the input or output pixelformat is not supported */
|
||||
if(!sws_isSupportedInput(in_pf)) {
|
||||
Warning("swscale does not support the input format: %c%c%c%c",(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff));
|
||||
}
|
||||
if(!sws_isSupportedOutput(out_pf)) {
|
||||
Warning("swscale does not support the output format: %c%c%c%c",(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check the buffer sizes */
|
||||
size_t insize = avpicture_get_size(in_pf, width, height);
|
||||
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;
|
||||
}
|
||||
size_t outsize = avpicture_get_size(out_pf, width, height);
|
||||
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;
|
||||
}
|
||||
/* 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
|
||||
|
||||
/* Get the context */
|
||||
swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL );
|
||||
if(swscale_ctx == NULL) {
|
||||
Error("Failed getting swscale context");
|
||||
return -6;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* Fill in the buffers */
|
||||
if(!avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer, in_pf, width, height ) ) {
|
||||
Error("Failed filling input frame with input buffer");
|
||||
return -7;
|
||||
}
|
||||
if(!avpicture_fill( (AVPicture*)output_avframe, out_buffer, out_pf, width, height ) ) {
|
||||
Error("Failed filling output frame with output buffer");
|
||||
return -8;
|
||||
}
|
||||
/* Get the context */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Do the conversion */
|
||||
if(!sws_scale(swscale_ctx, input_avframe->data, input_avframe->linesize, 0, height, output_avframe->data, output_avframe->linesize ) ) {
|
||||
Error("swscale conversion failed");
|
||||
return -10;
|
||||
}
|
||||
/* Fill in the buffers */
|
||||
#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 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Do the conversion */
|
||||
if(!sws_scale(swscale_ctx, input_avframe->data, input_avframe->linesize, 0, height, output_avframe->data, output_avframe->linesize ) ) {
|
||||
Error("swscale conversion failed");
|
||||
return -10;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SWScale::Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) {
|
||||
if(img->Width() != width) {
|
||||
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
|
||||
return -12;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if(img->Height() != height) {
|
||||
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
|
||||
return -13;
|
||||
}
|
||||
|
||||
return Convert(img->Buffer(),img->Size(),out_buffer,out_buffer_size,in_pf,out_pf,width,height);
|
||||
return Convert(img->Buffer(),img->Size(),out_buffer,out_buffer_size,in_pf,out_pf,width,height);
|
||||
}
|
||||
|
||||
int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size) {
|
||||
|
||||
if(!gotdefaults) {
|
||||
Error("Defaults are not set");
|
||||
return -24;
|
||||
}
|
||||
if(!gotdefaults) {
|
||||
Error("Defaults are not set");
|
||||
return -24;
|
||||
}
|
||||
|
||||
return Convert(img,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height);
|
||||
return Convert(img,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height);
|
||||
}
|
||||
|
||||
int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size) {
|
||||
|
||||
if(!gotdefaults) {
|
||||
Error("Defaults are not set");
|
||||
return -24;
|
||||
}
|
||||
if(!gotdefaults) {
|
||||
Error("Defaults are not set");
|
||||
return -24;
|
||||
}
|
||||
|
||||
return Convert(in_buffer,in_buffer_size,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height);
|
||||
return Convert(in_buffer,in_buffer_size,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height);
|
||||
}
|
||||
#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, " ");
|
||||
}
|
||||
|
|
208
src/zm_ffmpeg.h
208
src/zm_ffmpeg.h
|
@ -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>
|
||||
|
@ -39,69 +40,75 @@ extern "C" {
|
|||
* b and c the minor and micro versions of libav
|
||||
* d and e the minor and micro versions of FFmpeg */
|
||||
#define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \
|
||||
( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
|
||||
#if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0)
|
||||
#include <libavutil/opt.h>
|
||||
#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>
|
||||
#include <ffmpeg/mathematics.h>
|
||||
#include <ffmpeg/opt.h>
|
||||
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
||||
|
||||
|
||||
#if defined(HAVE_LIBAVUTIL_AVUTIL_H)
|
||||
#if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100)
|
||||
#define _AVPIXELFORMAT AVPixelFormat
|
||||
#define _AVPIXELFORMAT AVPixelFormat
|
||||
#else
|
||||
#define _AVPIXELFORMAT PixelFormat
|
||||
#define AV_PIX_FMT_NONE PIX_FMT_NONE
|
||||
#define AV_PIX_FMT_RGB444 PIX_FMT_RGB444
|
||||
#define AV_PIX_FMT_RGB555 PIX_FMT_RGB555
|
||||
#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
|
||||
#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
|
||||
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
|
||||
#define AV_PIX_FMT_BGRA PIX_FMT_BGRA
|
||||
#define AV_PIX_FMT_ARGB PIX_FMT_ARGB
|
||||
#define AV_PIX_FMT_ABGR PIX_FMT_ABGR
|
||||
#define AV_PIX_FMT_RGBA PIX_FMT_RGBA
|
||||
#define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8
|
||||
#define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422
|
||||
#define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P
|
||||
#define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P
|
||||
#define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
|
||||
#define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P
|
||||
#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
|
||||
#define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
|
||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||
#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
|
||||
#define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
|
||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||
#define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411
|
||||
#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
|
||||
#define AV_PIX_FMT_BGR555 PIX_FMT_BGR555
|
||||
#define AV_PIX_FMT_BGR8 PIX_FMT_BGR8
|
||||
#define AV_PIX_FMT_BGR4 PIX_FMT_BGR4
|
||||
#define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE
|
||||
#define AV_PIX_FMT_RGB8 PIX_FMT_RGB8
|
||||
#define AV_PIX_FMT_RGB4 PIX_FMT_RGB4
|
||||
#define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE
|
||||
#define AV_PIX_FMT_NV12 PIX_FMT_NV12
|
||||
#define AV_PIX_FMT_NV21 PIX_FMT_NV21
|
||||
#define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1
|
||||
#define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1
|
||||
#define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE
|
||||
#define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE
|
||||
#define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P
|
||||
#define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
|
||||
#define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P
|
||||
//#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264
|
||||
//#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1
|
||||
//#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2
|
||||
#define _AVPIXELFORMAT PixelFormat
|
||||
#define AV_PIX_FMT_NONE PIX_FMT_NONE
|
||||
#define AV_PIX_FMT_RGB444 PIX_FMT_RGB444
|
||||
#define AV_PIX_FMT_RGB555 PIX_FMT_RGB555
|
||||
#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
|
||||
#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
|
||||
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
|
||||
#define AV_PIX_FMT_BGRA PIX_FMT_BGRA
|
||||
#define AV_PIX_FMT_ARGB PIX_FMT_ARGB
|
||||
#define AV_PIX_FMT_ABGR PIX_FMT_ABGR
|
||||
#define AV_PIX_FMT_RGBA PIX_FMT_RGBA
|
||||
#define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8
|
||||
#define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422
|
||||
#define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P
|
||||
#define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P
|
||||
#define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
|
||||
#define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P
|
||||
#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
|
||||
#define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
|
||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||
#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
|
||||
#define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
|
||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||
#define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411
|
||||
#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
|
||||
#define AV_PIX_FMT_BGR555 PIX_FMT_BGR555
|
||||
#define AV_PIX_FMT_BGR8 PIX_FMT_BGR8
|
||||
#define AV_PIX_FMT_BGR4 PIX_FMT_BGR4
|
||||
#define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE
|
||||
#define AV_PIX_FMT_RGB8 PIX_FMT_RGB8
|
||||
#define AV_PIX_FMT_RGB4 PIX_FMT_RGB4
|
||||
#define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE
|
||||
#define AV_PIX_FMT_NV12 PIX_FMT_NV12
|
||||
#define AV_PIX_FMT_NV21 PIX_FMT_NV21
|
||||
#define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1
|
||||
#define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1
|
||||
#define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE
|
||||
#define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE
|
||||
#define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P
|
||||
#define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
|
||||
#define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P
|
||||
//#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264
|
||||
//#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1
|
||||
//#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2
|
||||
#endif
|
||||
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
||||
|
||||
|
@ -116,8 +123,8 @@ extern "C" {
|
|||
* b and c the minor and micro versions of libav
|
||||
* d and e the minor and micro versions of FFmpeg */
|
||||
#define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \
|
||||
( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
|
||||
#elif HAVE_FFMPEG_AVCODEC_H
|
||||
#include <ffmpeg/avcodec.h>
|
||||
|
@ -125,9 +132,9 @@ extern "C" {
|
|||
|
||||
#if defined(HAVE_LIBAVCODEC_AVCODEC_H)
|
||||
#if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100)
|
||||
#define _AVCODECID AVCodecID
|
||||
#define _AVCODECID AVCodecID
|
||||
#else
|
||||
#define _AVCODECID CodecID
|
||||
#define _AVCODECID CodecID
|
||||
#endif
|
||||
#endif /* HAVE_LIBAVCODEC_AVCODEC_H */
|
||||
|
||||
|
@ -141,8 +148,8 @@ extern "C" {
|
|||
* b and c the minor and micro versions of libav
|
||||
* d and e the minor and micro versions of FFmpeg */
|
||||
#define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \
|
||||
( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
|
||||
#elif HAVE_FFMPEG_AVFORMAT_H
|
||||
#include <ffmpeg/avformat.h>
|
||||
|
@ -157,8 +164,8 @@ extern "C" {
|
|||
* b and c the minor and micro versions of libav
|
||||
* d and e the minor and micro versions of FFmpeg */
|
||||
#define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \
|
||||
( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
|
||||
#elif HAVE_FFMPEG_AVDEVICE_H
|
||||
#include <ffmpeg/avdevice.h>
|
||||
|
@ -173,8 +180,8 @@ extern "C" {
|
|||
* b and c the minor and micro versions of libav
|
||||
* d and e the minor and micro versions of FFmpeg */
|
||||
#define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \
|
||||
( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||
(LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||
|
||||
#elif HAVE_FFMPEG_SWSCALE_H
|
||||
#include <ffmpeg/swscale.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
|
||||
|
@ -203,23 +213,23 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp
|
|||
#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||
class SWScale {
|
||||
public:
|
||||
SWScale();
|
||||
~SWScale();
|
||||
int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||
int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||
int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
SWScale();
|
||||
~SWScale();
|
||||
int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||
int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||
int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||
|
||||
protected:
|
||||
bool gotdefaults;
|
||||
struct SwsContext* swscale_ctx;
|
||||
AVFrame* input_avframe;
|
||||
AVFrame* output_avframe;
|
||||
enum _AVPIXELFORMAT default_input_pf;
|
||||
enum _AVPIXELFORMAT default_output_pf;
|
||||
unsigned int default_width;
|
||||
unsigned int default_height;
|
||||
bool gotdefaults;
|
||||
struct SwsContext* swscale_ctx;
|
||||
AVFrame* input_avframe;
|
||||
AVFrame* output_avframe;
|
||||
enum _AVPIXELFORMAT default_input_pf;
|
||||
enum _AVPIXELFORMAT default_output_pf;
|
||||
unsigned int default_width;
|
||||
unsigned int default_height;
|
||||
};
|
||||
#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||
|
||||
|
@ -256,23 +266,55 @@ protected:
|
|||
*/
|
||||
#ifdef __cplusplus
|
||||
|
||||
inline static const std::string av_make_error_string(int errnum)
|
||||
{
|
||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||
inline static const std::string av_make_error_string(int errnum)
|
||||
{
|
||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||
#if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0)
|
||||
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
||||
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
||||
#else
|
||||
snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum);
|
||||
snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum);
|
||||
#endif
|
||||
return (std::string)errbuf;
|
||||
}
|
||||
return (std::string)errbuf;
|
||||
}
|
||||
|
||||
#undef av_err2str
|
||||
#define av_err2str(errnum) av_make_error_string(errnum).c_str()
|
||||
#undef av_err2str
|
||||
#define av_err2str(errnum) av_make_error_string(errnum).c_str()
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // __cplusplus
|
||||
|
||||
|
||||
#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,76 +23,84 @@
|
|||
|
||||
#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
|
||||
|
||||
#ifdef SOLARIS
|
||||
#include <sys/errno.h> // for ESRCH
|
||||
#include <sys/errno.h> // for ESRCH
|
||||
#include <signal.h>
|
||||
#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 ),
|
||||
mPath( p_path ),
|
||||
mMethod( p_method ),
|
||||
mOptions( p_options )
|
||||
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 )
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
mFormatContext = NULL;
|
||||
mVideoStreamId = -1;
|
||||
mCodecContext = NULL;
|
||||
mCodec = NULL;
|
||||
mRawFrame = NULL;
|
||||
mFrame = NULL;
|
||||
frameCount = 0;
|
||||
mIsOpening = false;
|
||||
mCanCapture = false;
|
||||
mOpenStart = 0;
|
||||
mReopenThread = 0;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
mConvertContext = NULL;
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
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;
|
||||
#endif
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
||||
imagePixFormat = AV_PIX_FMT_RGBA;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
||||
imagePixFormat = AV_PIX_FMT_RGB24;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
imagePixFormat = AV_PIX_FMT_GRAY8;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
||||
imagePixFormat = AV_PIX_FMT_RGBA;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
||||
imagePixFormat = AV_PIX_FMT_RGB24;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
imagePixFormat = AV_PIX_FMT_GRAY8;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FfmpegCamera::~FfmpegCamera()
|
||||
{
|
||||
CloseFfmpeg();
|
||||
CloseFfmpeg();
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void FfmpegCamera::Initialise()
|
||||
{
|
||||
if ( logDebugging() )
|
||||
av_log_set_level( AV_LOG_DEBUG );
|
||||
else
|
||||
av_log_set_level( AV_LOG_QUIET );
|
||||
if ( logDebugging() )
|
||||
av_log_set_level( AV_LOG_DEBUG );
|
||||
else
|
||||
av_log_set_level( AV_LOG_QUIET );
|
||||
|
||||
av_register_all();
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
}
|
||||
|
||||
void FfmpegCamera::Terminate()
|
||||
|
@ -101,373 +109,587 @@ void FfmpegCamera::Terminate()
|
|||
|
||||
int FfmpegCamera::PrimeCapture()
|
||||
{
|
||||
Info( "Priming capture from %s", mPath.c_str() );
|
||||
mVideoStreamId = -1;
|
||||
mAudioStreamId = -1;
|
||||
Info( "Priming capture from %s", mPath.c_str() );
|
||||
|
||||
if (OpenFfmpeg() != 0){
|
||||
ReopenFfmpeg();
|
||||
}
|
||||
return 0;
|
||||
if (OpenFfmpeg() != 0){
|
||||
ReopenFfmpeg();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FfmpegCamera::PreCapture()
|
||||
{
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int FfmpegCamera::Capture( Image &image )
|
||||
{
|
||||
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.");
|
||||
if (!mCanCapture){
|
||||
return -1;
|
||||
}
|
||||
|
||||
Info( "Successfully reopened stream." );
|
||||
mReopenThread = 0;
|
||||
}
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
||||
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( 3, "Got frame %d", frameCount );
|
||||
|
||||
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
|
||||
/* 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 HAVE_LIBSWSCALE
|
||||
if(mConvertContext == NULL) {
|
||||
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
||||
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();
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
|
||||
av_packet_unref( &packet);
|
||||
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 );
|
||||
// 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)
|
||||
if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 )
|
||||
#else
|
||||
av_free_packet( &packet );
|
||||
if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 )
|
||||
#endif
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
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 );
|
||||
|
||||
#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) {
|
||||
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++;
|
||||
} // 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()
|
||||
{
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
// Nothing to do here
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int FfmpegCamera::OpenFfmpeg() {
|
||||
|
||||
Debug ( 2, "OpenFfmpeg called." );
|
||||
Debug ( 2, "OpenFfmpeg called." );
|
||||
|
||||
mOpenStart = time(NULL);
|
||||
mIsOpening = true;
|
||||
mOpenStart = time(NULL);
|
||||
mIsOpening = true;
|
||||
|
||||
// Open the input, not necessarily a file
|
||||
// Open the input, not necessarily a file
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0)
|
||||
Debug ( 1, "Calling av_open_input_file" );
|
||||
if ( av_open_input_file( &mFormatContext, mPath.c_str(), NULL, 0, NULL ) !=0 )
|
||||
Debug ( 1, "Calling av_open_input_file" );
|
||||
if ( av_open_input_file( &mFormatContext, mPath.c_str(), NULL, 0, NULL ) !=0 )
|
||||
#else
|
||||
// Handle options
|
||||
AVDictionary *opts = 0;
|
||||
StringVector opVect = split(Options(), ",");
|
||||
|
||||
// Set transport method as specified by method field, rtpUni is default
|
||||
if ( Method() == "rtpMulti" )
|
||||
opVect.push_back("rtsp_transport=udp_multicast");
|
||||
else if ( Method() == "rtpRtsp" )
|
||||
opVect.push_back("rtsp_transport=tcp");
|
||||
else if ( Method() == "rtpRtspHttp" )
|
||||
opVect.push_back("rtsp_transport=http");
|
||||
|
||||
Debug(2, "Number of Options: %d",opVect.size());
|
||||
for (size_t i=0; i<opVect.size(); i++)
|
||||
{
|
||||
StringVector parts = split(opVect[i],"=");
|
||||
if (parts.size() > 1) {
|
||||
parts[0] = trimSpaces(parts[0]);
|
||||
parts[1] = trimSpaces(parts[1]);
|
||||
if ( av_dict_set(&opts, parts[0].c_str(), parts[1].c_str(), 0) == 0 ) {
|
||||
Debug(2, "set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Error trying to set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str() );
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Unable to parse ffmpeg option %d '%s', expecting key=value", i, opVect[i].c_str() );
|
||||
}
|
||||
}
|
||||
Debug ( 1, "Calling avformat_open_input" );
|
||||
// Handle options
|
||||
AVDictionary *opts = 0;
|
||||
StringVector opVect = split(Options(), ",");
|
||||
|
||||
// Set transport method as specified by method field, rtpUni is default
|
||||
if ( Method() == "rtpMulti" )
|
||||
opVect.push_back("rtsp_transport=udp_multicast");
|
||||
else if ( Method() == "rtpRtsp" )
|
||||
opVect.push_back("rtsp_transport=tcp");
|
||||
else if ( Method() == "rtpRtspHttp" )
|
||||
opVect.push_back("rtsp_transport=http");
|
||||
|
||||
Debug(2, "Number of Options: %d",opVect.size());
|
||||
for (size_t i=0; i<opVect.size(); i++)
|
||||
{
|
||||
StringVector parts = split(opVect[i],"=");
|
||||
if (parts.size() > 1) {
|
||||
parts[0] = trimSpaces(parts[0]);
|
||||
parts[1] = trimSpaces(parts[1]);
|
||||
if ( av_dict_set(&opts, parts[0].c_str(), parts[1].c_str(), 0) == 0 ) {
|
||||
Debug(2, "set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Error trying to set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str() );
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Unable to parse ffmpeg option %d '%s', expecting key=value", i, opVect[i].c_str() );
|
||||
}
|
||||
}
|
||||
Debug ( 1, "Calling avformat_open_input" );
|
||||
|
||||
mFormatContext = avformat_alloc_context( );
|
||||
mFormatContext->interrupt_callback.callback = FfmpegInterruptCallback;
|
||||
mFormatContext->interrupt_callback.opaque = this;
|
||||
mFormatContext = avformat_alloc_context( );
|
||||
mFormatContext->interrupt_callback.callback = FfmpegInterruptCallback;
|
||||
mFormatContext->interrupt_callback.opaque = this;
|
||||
|
||||
if ( avformat_open_input( &mFormatContext, mPath.c_str(), NULL, &opts ) !=0 )
|
||||
#endif
|
||||
{
|
||||
mIsOpening = false;
|
||||
Error( "Unable to open input %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVDictionaryEntry *e;
|
||||
if ((e = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX)) != NULL) {
|
||||
Warning( "Option %s not recognized by ffmpeg", e->key);
|
||||
}
|
||||
|
||||
mIsOpening = false;
|
||||
Debug ( 1, "Opened input" );
|
||||
|
||||
// Locate stream info from avformat_open_input
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0)
|
||||
Debug ( 1, "Calling av_find_stream_info" );
|
||||
if ( av_find_stream_info( mFormatContext ) < 0 )
|
||||
#else
|
||||
Debug ( 1, "Calling avformat_find_stream_info" );
|
||||
if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 )
|
||||
#endif
|
||||
Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||
|
||||
Debug ( 1, "Got stream info" );
|
||||
|
||||
// Find first video stream present
|
||||
mVideoStreamId = -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))
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
|
||||
#else
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
||||
if ( avformat_open_input( &mFormatContext, mPath.c_str(), NULL, &opts ) !=0 )
|
||||
#endif
|
||||
{
|
||||
mVideoStreamId = i;
|
||||
break;
|
||||
mIsOpening = false;
|
||||
Error( "Unable to open input %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if ( mVideoStreamId == -1 )
|
||||
Fatal( "Unable to locate video stream in %s", mPath.c_str() );
|
||||
|
||||
Debug ( 1, "Found video stream" );
|
||||
AVDictionaryEntry *e;
|
||||
if ((e = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX)) != NULL) {
|
||||
Warning( "Option %s not recognized by ffmpeg", e->key);
|
||||
}
|
||||
|
||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||
mIsOpening = false;
|
||||
Debug ( 1, "Opened input" );
|
||||
|
||||
// Try and get the codec from the codec context
|
||||
if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL )
|
||||
Fatal( "Can't find codec for video stream from %s", mPath.c_str() );
|
||||
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" );
|
||||
if ( av_find_stream_info( mFormatContext ) < 0 )
|
||||
#else
|
||||
Debug ( 1, "Calling avformat_find_stream_info" );
|
||||
if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 )
|
||||
#endif
|
||||
Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||
|
||||
Debug ( 1, "Found decoder" );
|
||||
Debug ( 1, "Got stream info" );
|
||||
|
||||
// Open the codec
|
||||
// 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))
|
||||
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;
|
||||
// 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 ( 3, "Found video stream at index %d", mVideoStreamId );
|
||||
Debug ( 3, "Found audio stream at index %d", mAudioStreamId );
|
||||
|
||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||
|
||||
// Try and get the codec from the codec context
|
||||
if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL )
|
||||
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)
|
||||
Debug ( 1, "Calling avcodec_open" );
|
||||
if ( avcodec_open( mCodecContext, mCodec ) < 0 )
|
||||
Debug ( 1, "Calling avcodec_open" );
|
||||
if ( avcodec_open( mCodecContext, mCodec ) < 0 )
|
||||
#else
|
||||
Debug ( 1, "Calling avcodec_open2" );
|
||||
if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 )
|
||||
Debug ( 1, "Calling avcodec_open2" );
|
||||
if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 )
|
||||
#endif
|
||||
Fatal( "Unable to open codec for video stream from %s", mPath.c_str() );
|
||||
Fatal( "Unable to open codec for video stream from %s", mPath.c_str() );
|
||||
|
||||
Debug ( 1, "Opened codec" );
|
||||
Debug ( 1, "Opened codec" );
|
||||
|
||||
// Allocate space for the native video frame
|
||||
// Allocate space for the native video frame
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
mRawFrame = av_frame_alloc();
|
||||
mRawFrame = av_frame_alloc();
|
||||
#else
|
||||
mRawFrame = avcodec_alloc_frame();
|
||||
mRawFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
|
||||
// Allocate space for the converted video frame
|
||||
// Allocate space for the converted video frame
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
mFrame = av_frame_alloc();
|
||||
mFrame = av_frame_alloc();
|
||||
#else
|
||||
mFrame = avcodec_alloc_frame();
|
||||
mFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
|
||||
if(mRawFrame == NULL || mFrame == NULL)
|
||||
Fatal( "Unable to allocate frame for %s", mPath.c_str() );
|
||||
if(mRawFrame == NULL || mFrame == NULL)
|
||||
Fatal( "Unable to allocate frame for %s", mPath.c_str() );
|
||||
|
||||
Debug ( 1, "Allocated frames" );
|
||||
|
||||
int pSize = avpicture_get_size( imagePixFormat, width, height );
|
||||
if( (unsigned int)pSize != imagesize) {
|
||||
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
|
||||
}
|
||||
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 %d", pSize );
|
||||
|
||||
Debug ( 1, "Validated imagesize" );
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
Debug ( 1, "Calling sws_isSupportedInput" );
|
||||
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
|
||||
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
|
||||
}
|
||||
|
||||
if(!sws_isSupportedOutput(imagePixFormat)) {
|
||||
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
||||
}
|
||||
|
||||
Debug ( 1, "Calling sws_isSupportedInput" );
|
||||
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
|
||||
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
|
||||
}
|
||||
|
||||
if(!sws_isSupportedOutput(imagePixFormat)) {
|
||||
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
||||
}
|
||||
|
||||
#else // HAVE_LIBSWSCALE
|
||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
|
||||
mCanCapture = true;
|
||||
mCanCapture = true;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FfmpegCamera::ReopenFfmpeg() {
|
||||
|
||||
Debug(2, "ReopenFfmpeg called.");
|
||||
Debug(2, "ReopenFfmpeg called.");
|
||||
|
||||
mCanCapture = false;
|
||||
if (pthread_create( &mReopenThread, NULL, ReopenFfmpegThreadCallback, (void*) this) != 0){
|
||||
// Log a fatal error and exit the process.
|
||||
Fatal( "ReopenFfmpeg failed to create worker thread." );
|
||||
}
|
||||
mCanCapture = false;
|
||||
if (pthread_create( &mReopenThread, NULL, ReopenFfmpegThreadCallback, (void*) this) != 0){
|
||||
// Log a fatal error and exit the process.
|
||||
Fatal( "ReopenFfmpeg failed to create worker thread." );
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FfmpegCamera::CloseFfmpeg(){
|
||||
|
||||
Debug(2, "CloseFfmpeg called.");
|
||||
Debug(2, "CloseFfmpeg called.");
|
||||
|
||||
mCanCapture = false;
|
||||
mCanCapture = false;
|
||||
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
av_frame_free( &mFrame );
|
||||
av_frame_free( &mRawFrame );
|
||||
av_frame_free( &mFrame );
|
||||
av_frame_free( &mRawFrame );
|
||||
#else
|
||||
av_freep( &mFrame );
|
||||
av_freep( &mRawFrame );
|
||||
av_freep( &mFrame );
|
||||
av_freep( &mRawFrame );
|
||||
#endif
|
||||
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
if ( mConvertContext )
|
||||
{
|
||||
sws_freeContext( mConvertContext );
|
||||
mConvertContext = NULL;
|
||||
}
|
||||
if ( mConvertContext )
|
||||
{
|
||||
sws_freeContext( mConvertContext );
|
||||
mConvertContext = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( mCodecContext )
|
||||
{
|
||||
avcodec_close( mCodecContext );
|
||||
mCodecContext = NULL; // Freed by av_close_input_file
|
||||
}
|
||||
if ( mFormatContext )
|
||||
{
|
||||
if ( mCodecContext )
|
||||
{
|
||||
avcodec_close( mCodecContext );
|
||||
mCodecContext = NULL; // Freed by av_close_input_file
|
||||
}
|
||||
if ( mFormatContext )
|
||||
{
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
||||
av_close_input_file( mFormatContext );
|
||||
av_close_input_file( mFormatContext );
|
||||
#else
|
||||
avformat_close_input( &mFormatContext );
|
||||
avformat_close_input( &mFormatContext );
|
||||
#endif
|
||||
mFormatContext = NULL;
|
||||
}
|
||||
mFormatContext = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FfmpegCamera::FfmpegInterruptCallback(void *ctx)
|
||||
{
|
||||
FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
||||
if (camera->mIsOpening){
|
||||
int now = time(NULL);
|
||||
if ((now - camera->mOpenStart) > config.ffmpeg_open_timeout) {
|
||||
Error ( "Open video took more than %d seconds.", config.ffmpeg_open_timeout );
|
||||
return 1;
|
||||
FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
||||
if (camera->mIsOpening){
|
||||
int now = time(NULL);
|
||||
if ((now - camera->mOpenStart) > config.ffmpeg_open_timeout) {
|
||||
Error ( "Open video took more than %d seconds.", config.ffmpeg_open_timeout );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
|
||||
if (ctx == NULL) return NULL;
|
||||
if (ctx == NULL) return NULL;
|
||||
|
||||
FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
||||
FfmpegCamera* camera = reinterpret_cast<FfmpegCamera*>(ctx);
|
||||
|
||||
while (1){
|
||||
// Close current stream.
|
||||
camera->CloseFfmpeg();
|
||||
while (1){
|
||||
// Close current stream.
|
||||
camera->CloseFfmpeg();
|
||||
|
||||
// Sleep if necessary to not reconnect too fast.
|
||||
int wait = config.ffmpeg_open_timeout - (time(NULL) - camera->mOpenStart);
|
||||
wait = wait < 0 ? 0 : wait;
|
||||
if (wait > 0){
|
||||
Debug( 1, "Sleeping %d seconds before reopening stream.", wait );
|
||||
sleep(wait);
|
||||
// Sleep if necessary to not reconnect too fast.
|
||||
int wait = config.ffmpeg_open_timeout - (time(NULL) - camera->mOpenStart);
|
||||
wait = wait < 0 ? 0 : wait;
|
||||
if (wait > 0){
|
||||
Debug( 1, "Sleeping %d seconds before reopening stream.", wait );
|
||||
sleep(wait);
|
||||
}
|
||||
|
||||
if (camera->OpenFfmpeg() == 0){
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
if (camera->OpenFfmpeg() == 0){
|
||||
return NULL;
|
||||
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
|
||||
|
@ -33,51 +34,62 @@
|
|||
class FfmpegCamera : public Camera
|
||||
{
|
||||
protected:
|
||||
std::string mPath;
|
||||
std::string mMethod;
|
||||
std::string mOptions;
|
||||
std::string mPath;
|
||||
std::string mMethod;
|
||||
std::string mOptions;
|
||||
|
||||
int frameCount;
|
||||
int frameCount;
|
||||
|
||||
#if HAVE_LIBAVFORMAT
|
||||
AVFormatContext *mFormatContext;
|
||||
int mVideoStreamId;
|
||||
AVCodecContext *mCodecContext;
|
||||
AVCodec *mCodec;
|
||||
AVFrame *mRawFrame;
|
||||
AVFrame *mFrame;
|
||||
_AVPIXELFORMAT imagePixFormat;
|
||||
AVFormatContext *mFormatContext;
|
||||
int mVideoStreamId;
|
||||
int mAudioStreamId;
|
||||
AVCodecContext *mCodecContext;
|
||||
AVCodec *mCodec;
|
||||
AVFrame *mRawFrame;
|
||||
AVFrame *mFrame;
|
||||
_AVPIXELFORMAT imagePixFormat;
|
||||
|
||||
int OpenFfmpeg();
|
||||
int ReopenFfmpeg();
|
||||
int CloseFfmpeg();
|
||||
static int FfmpegInterruptCallback(void *ctx);
|
||||
static void* ReopenFfmpegThreadCallback(void *ctx);
|
||||
bool mIsOpening;
|
||||
bool mCanCapture;
|
||||
int mOpenStart;
|
||||
pthread_t mReopenThread;
|
||||
int OpenFfmpeg();
|
||||
int ReopenFfmpeg();
|
||||
int CloseFfmpeg();
|
||||
static int FfmpegInterruptCallback(void *ctx);
|
||||
static void* ReopenFfmpegThreadCallback(void *ctx);
|
||||
bool mIsOpening;
|
||||
bool mCanCapture;
|
||||
int mOpenStart;
|
||||
pthread_t mReopenThread;
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
||||
bool wasRecording;
|
||||
VideoStore *videoStore;
|
||||
char oldDirectory[4096];
|
||||
|
||||
// Last Key frame
|
||||
AVPacket lastKeyframePkt;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
struct SwsContext *mConvertContext;
|
||||
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();
|
||||
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 ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
const std::string &Method() const { return( mMethod ); }
|
||||
const std::string &Path() const { return( mPath ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
const std::string &Method() const { return( mMethod ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
||||
int PostCapture();
|
||||
};
|
||||
|
||||
#endif // ZM_FFMPEG_CAMERA_H
|
||||
|
|
|
@ -34,30 +34,30 @@
|
|||
#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 )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
strncpy( path, p_path, sizeof(path) );
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
}
|
||||
|
||||
FileCamera::~FileCamera()
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void FileCamera::Initialise()
|
||||
{
|
||||
if ( !path[0] )
|
||||
{
|
||||
Error( "No path specified for file image" );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( !path[0] )
|
||||
{
|
||||
Error( "No path specified for file image" );
|
||||
exit( -1 );
|
||||
}
|
||||
}
|
||||
|
||||
void FileCamera::Terminate()
|
||||
|
@ -66,26 +66,26 @@ void FileCamera::Terminate()
|
|||
|
||||
int FileCamera::PreCapture()
|
||||
{
|
||||
struct stat statbuf;
|
||||
if ( stat( path, &statbuf ) < 0 )
|
||||
{
|
||||
Error( "Can't stat %s: %s", path, strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
struct stat statbuf;
|
||||
if ( stat( path, &statbuf ) < 0 )
|
||||
{
|
||||
Error( "Can't stat %s: %s", path, strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
while ( (time( 0 ) - statbuf.st_mtime) < 1 )
|
||||
{
|
||||
usleep( 100000 );
|
||||
}
|
||||
return( 0 );
|
||||
while ( (time( 0 ) - statbuf.st_mtime) < 1 )
|
||||
{
|
||||
usleep( 100000 );
|
||||
}
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int FileCamera::Capture( Image &image )
|
||||
{
|
||||
return( image.ReadJpeg( path, colours, subpixelorder )?0:-1 );
|
||||
return( image.ReadJpeg( path, colours, subpixelorder )?0:-1 );
|
||||
}
|
||||
|
||||
int FileCamera::PostCapture()
|
||||
{
|
||||
return( 0 );
|
||||
return( 0 );
|
||||
}
|
||||
|
|
|
@ -33,19 +33,20 @@
|
|||
class FileCamera : public Camera
|
||||
{
|
||||
protected:
|
||||
char path[PATH_MAX];
|
||||
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();
|
||||
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 ); }
|
||||
const char *Path() const { return( path ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
||||
};
|
||||
|
||||
#endif // ZM_FILE_CAMERA_H
|
||||
|
|
|
@ -25,102 +25,102 @@
|
|||
// Do all the buffer checking work here to avoid unnecessary locking
|
||||
void* LibvlcLockBuffer(void* opaque, void** planes)
|
||||
{
|
||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||
data->mutex.lock();
|
||||
|
||||
uint8_t* buffer = data->buffer;
|
||||
data->buffer = data->prevBuffer;
|
||||
data->prevBuffer = buffer;
|
||||
|
||||
*planes = data->buffer;
|
||||
return NULL;
|
||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||
data->mutex.lock();
|
||||
|
||||
uint8_t* buffer = data->buffer;
|
||||
data->buffer = data->prevBuffer;
|
||||
data->prevBuffer = buffer;
|
||||
|
||||
*planes = data->buffer;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
|
||||
{
|
||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||
|
||||
bool newFrame = false;
|
||||
for(uint32_t i = 0; i < data->bufferSize; i++)
|
||||
{
|
||||
if(data->buffer[i] != data->prevBuffer[i])
|
||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||
|
||||
bool newFrame = false;
|
||||
for(uint32_t i = 0; i < data->bufferSize; i++)
|
||||
{
|
||||
newFrame = true;
|
||||
break;
|
||||
if(data->buffer[i] != data->prevBuffer[i])
|
||||
{
|
||||
newFrame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data->mutex.unlock();
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
|
||||
if(newFrame || difftime(now, data->prevTime) >= 0.8)
|
||||
{
|
||||
data->prevTime = now;
|
||||
data->newImage.updateValueSignal(true);
|
||||
}
|
||||
}
|
||||
data->mutex.unlock();
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
|
||||
if(newFrame || difftime(now, data->prevTime) >= 0.8)
|
||||
{
|
||||
data->prevTime = now;
|
||||
data->newImage.updateValueSignal(true);
|
||||
}
|
||||
}
|
||||
|
||||
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 ),
|
||||
mPath( p_path ),
|
||||
mMethod( p_method ),
|
||||
mOptions( p_options )
|
||||
{
|
||||
mLibvlcInstance = NULL;
|
||||
mLibvlcMedia = NULL;
|
||||
mLibvlcMediaPlayer = NULL;
|
||||
mLibvlcData.buffer = NULL;
|
||||
mLibvlcData.prevBuffer = NULL;
|
||||
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 )
|
||||
{
|
||||
mLibvlcInstance = NULL;
|
||||
mLibvlcMedia = NULL;
|
||||
mLibvlcMediaPlayer = NULL;
|
||||
mLibvlcData.buffer = NULL;
|
||||
mLibvlcData.prevBuffer = NULL;
|
||||
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_BGRA;
|
||||
mTargetChroma = "RV32";
|
||||
mBpp = 4;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_BGR;
|
||||
mTargetChroma = "RV24";
|
||||
mBpp = 3;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
mTargetChroma = "GREY";
|
||||
mBpp = 1;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_BGRA;
|
||||
mTargetChroma = "RV32";
|
||||
mBpp = 4;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_BGR;
|
||||
mTargetChroma = "RV24";
|
||||
mBpp = 3;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
mTargetChroma = "GREY";
|
||||
mBpp = 1;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
}
|
||||
|
||||
LibvlcCamera::~LibvlcCamera()
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
if(mLibvlcMediaPlayer != NULL)
|
||||
{
|
||||
libvlc_media_player_release(mLibvlcMediaPlayer);
|
||||
mLibvlcMediaPlayer = NULL;
|
||||
}
|
||||
if(mLibvlcMedia != NULL)
|
||||
{
|
||||
libvlc_media_release(mLibvlcMedia);
|
||||
mLibvlcMedia = NULL;
|
||||
}
|
||||
if(mLibvlcInstance != NULL)
|
||||
{
|
||||
libvlc_release(mLibvlcInstance);
|
||||
mLibvlcInstance = NULL;
|
||||
}
|
||||
if (mOptArgV != NULL)
|
||||
{
|
||||
delete[] mOptArgV;
|
||||
}
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
if(mLibvlcMediaPlayer != NULL)
|
||||
{
|
||||
libvlc_media_player_release(mLibvlcMediaPlayer);
|
||||
mLibvlcMediaPlayer = NULL;
|
||||
}
|
||||
if(mLibvlcMedia != NULL)
|
||||
{
|
||||
libvlc_media_release(mLibvlcMedia);
|
||||
mLibvlcMedia = NULL;
|
||||
}
|
||||
if(mLibvlcInstance != NULL)
|
||||
{
|
||||
libvlc_release(mLibvlcInstance);
|
||||
mLibvlcInstance = NULL;
|
||||
}
|
||||
if (mOptArgV != NULL)
|
||||
{
|
||||
delete[] mOptArgV;
|
||||
}
|
||||
}
|
||||
|
||||
void LibvlcCamera::Initialise()
|
||||
|
@ -129,91 +129,105 @@ void LibvlcCamera::Initialise()
|
|||
|
||||
void LibvlcCamera::Terminate()
|
||||
{
|
||||
libvlc_media_player_stop(mLibvlcMediaPlayer);
|
||||
if(mLibvlcData.buffer != NULL)
|
||||
{
|
||||
zm_freealigned(mLibvlcData.buffer);
|
||||
}
|
||||
if(mLibvlcData.prevBuffer != NULL)
|
||||
{
|
||||
zm_freealigned(mLibvlcData.prevBuffer);
|
||||
}
|
||||
libvlc_media_player_stop(mLibvlcMediaPlayer);
|
||||
if(mLibvlcData.buffer != NULL)
|
||||
{
|
||||
zm_freealigned(mLibvlcData.buffer);
|
||||
}
|
||||
if(mLibvlcData.prevBuffer != NULL)
|
||||
{
|
||||
zm_freealigned(mLibvlcData.prevBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
int LibvlcCamera::PrimeCapture()
|
||||
{
|
||||
Info("Priming capture from %s", mPath.c_str());
|
||||
|
||||
StringVector opVect = split(Options(), ",");
|
||||
|
||||
// Set transport method as specified by method field, rtpUni is default
|
||||
if ( Method() == "rtpMulti" )
|
||||
opVect.push_back("--rtsp-mcast");
|
||||
else if ( Method() == "rtpRtsp" )
|
||||
opVect.push_back("--rtsp-tcp");
|
||||
else if ( Method() == "rtpRtspHttp" )
|
||||
opVect.push_back("--rtsp-http");
|
||||
Info("Priming capture from %s", mPath.c_str());
|
||||
|
||||
StringVector opVect = split(Options(), ",");
|
||||
|
||||
// Set transport method as specified by method field, rtpUni is default
|
||||
if ( Method() == "rtpMulti" )
|
||||
opVect.push_back("--rtsp-mcast");
|
||||
else if ( Method() == "rtpRtsp" )
|
||||
opVect.push_back("--rtsp-tcp");
|
||||
else if ( Method() == "rtpRtspHttp" )
|
||||
opVect.push_back("--rtsp-http");
|
||||
|
||||
if (opVect.size() > 0)
|
||||
{
|
||||
mOptArgV = new char*[opVect.size()];
|
||||
Debug(2, "Number of Options: %d",opVect.size());
|
||||
for (size_t i=0; i< opVect.size(); i++) {
|
||||
opVect[i] = trimSpaces(opVect[i]);
|
||||
mOptArgV[i] = (char *)opVect[i].c_str();
|
||||
Debug(2, "set option %d to '%s'", i, opVect[i].c_str());
|
||||
if (opVect.size() > 0)
|
||||
{
|
||||
mOptArgV = new char*[opVect.size()];
|
||||
Debug(2, "Number of Options: %d",opVect.size());
|
||||
for (size_t i=0; i< opVect.size(); i++) {
|
||||
opVect[i] = trimSpaces(opVect[i]);
|
||||
mOptArgV[i] = (char *)opVect[i].c_str();
|
||||
Debug(2, "set option %d to '%s'", i, opVect[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV);
|
||||
if(mLibvlcInstance == NULL)
|
||||
Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg());
|
||||
|
||||
mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str());
|
||||
if(mLibvlcMedia == NULL)
|
||||
Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||
|
||||
mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia);
|
||||
if(mLibvlcMediaPlayer == NULL)
|
||||
Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||
mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV);
|
||||
if(mLibvlcInstance == NULL)
|
||||
Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg());
|
||||
|
||||
mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str());
|
||||
if(mLibvlcMedia == NULL)
|
||||
Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||
|
||||
mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia);
|
||||
if(mLibvlcMediaPlayer == NULL)
|
||||
Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||
|
||||
libvlc_video_set_format(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp);
|
||||
libvlc_video_set_callbacks(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, NULL, &mLibvlcData);
|
||||
libvlc_video_set_format(mLibvlcMediaPlayer, mTargetChroma.c_str(), width, height, width * mBpp);
|
||||
libvlc_video_set_callbacks(mLibvlcMediaPlayer, &LibvlcLockBuffer, &LibvlcUnlockBuffer, NULL, &mLibvlcData);
|
||||
|
||||
mLibvlcData.bufferSize = width * height * mBpp;
|
||||
// Libvlc wants 32 byte alignment for images (should in theory do this for all image lines)
|
||||
mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize);
|
||||
mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize);
|
||||
|
||||
mLibvlcData.newImage.setValueImmediate(false);
|
||||
mLibvlcData.bufferSize = width * height * mBpp;
|
||||
// Libvlc wants 32 byte alignment for images (should in theory do this for all image lines)
|
||||
mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize);
|
||||
mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize);
|
||||
|
||||
mLibvlcData.newImage.setValueImmediate(false);
|
||||
|
||||
libvlc_media_player_play(mLibvlcMediaPlayer);
|
||||
|
||||
return(0);
|
||||
libvlc_media_player_play(mLibvlcMediaPlayer);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
int LibvlcCamera::PreCapture()
|
||||
{
|
||||
return(0);
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
// Should not return -1 as cancels capture. Always wait for image if available.
|
||||
int LibvlcCamera::Capture( Image &image )
|
||||
{
|
||||
while(!mLibvlcData.newImage.getValueImmediate())
|
||||
mLibvlcData.newImage.getUpdatedValue(1);
|
||||
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);
|
||||
mLibvlcData.mutex.lock();
|
||||
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
|
||||
mLibvlcData.newImage.setValueImmediate(false);
|
||||
mLibvlcData.mutex.unlock();
|
||||
|
||||
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);
|
||||
return(0);
|
||||
}
|
||||
|
||||
#endif // HAVE_LIBVLC
|
||||
|
|
|
@ -33,44 +33,45 @@
|
|||
// Used by libvlc callbacks
|
||||
struct LibvlcPrivateData
|
||||
{
|
||||
uint8_t* buffer;
|
||||
uint8_t* prevBuffer;
|
||||
time_t prevTime;
|
||||
uint32_t bufferSize;
|
||||
Mutex mutex;
|
||||
ThreadData<bool> newImage;
|
||||
uint8_t* buffer;
|
||||
uint8_t* prevBuffer;
|
||||
time_t prevTime;
|
||||
uint32_t bufferSize;
|
||||
Mutex mutex;
|
||||
ThreadData<bool> newImage;
|
||||
};
|
||||
|
||||
class LibvlcCamera : public Camera
|
||||
{
|
||||
protected:
|
||||
std::string mPath;
|
||||
std::string mMethod;
|
||||
std::string mOptions;
|
||||
char **mOptArgV;
|
||||
LibvlcPrivateData mLibvlcData;
|
||||
std::string mTargetChroma;
|
||||
uint8_t mBpp;
|
||||
std::string mPath;
|
||||
std::string mMethod;
|
||||
std::string mOptions;
|
||||
char **mOptArgV;
|
||||
LibvlcPrivateData mLibvlcData;
|
||||
std::string mTargetChroma;
|
||||
uint8_t mBpp;
|
||||
|
||||
libvlc_instance_t *mLibvlcInstance;
|
||||
libvlc_media_t *mLibvlcMedia;
|
||||
libvlc_media_player_t *mLibvlcMediaPlayer;
|
||||
libvlc_instance_t *mLibvlcInstance;
|
||||
libvlc_media_t *mLibvlcMedia;
|
||||
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();
|
||||
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 ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
const std::string &Method() const { return( mMethod ); }
|
||||
const std::string &Path() const { return( mPath ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
const std::string &Method() const { return( mMethod ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
||||
int PostCapture();
|
||||
};
|
||||
|
||||
#endif // HAVE_LIBVLC
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -47,99 +47,118 @@ class LocalCamera : public Camera
|
|||
{
|
||||
protected:
|
||||
#if ZM_HAS_V4L2
|
||||
struct V4L2MappedBuffer
|
||||
{
|
||||
void *start;
|
||||
size_t length;
|
||||
};
|
||||
struct V4L2MappedBuffer
|
||||
{
|
||||
void *start;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
struct V4L2Data
|
||||
{
|
||||
v4l2_cropcap cropcap;
|
||||
v4l2_crop crop;
|
||||
v4l2_format fmt;
|
||||
v4l2_requestbuffers reqbufs;
|
||||
V4L2MappedBuffer *buffers;
|
||||
v4l2_buffer *bufptr;
|
||||
};
|
||||
struct V4L2Data
|
||||
{
|
||||
v4l2_cropcap cropcap;
|
||||
v4l2_crop crop;
|
||||
v4l2_format fmt;
|
||||
v4l2_requestbuffers reqbufs;
|
||||
V4L2MappedBuffer *buffers;
|
||||
v4l2_buffer *bufptr;
|
||||
};
|
||||
#endif // ZM_HAS_V4L2
|
||||
|
||||
#if ZM_HAS_V4L1
|
||||
struct V4L1Data
|
||||
{
|
||||
int active_frame;
|
||||
video_mbuf frames;
|
||||
video_mmap *buffers;
|
||||
unsigned char *bufptr;
|
||||
};
|
||||
struct V4L1Data
|
||||
{
|
||||
int active_frame;
|
||||
video_mbuf frames;
|
||||
video_mmap *buffers;
|
||||
unsigned char *bufptr;
|
||||
};
|
||||
#endif // ZM_HAS_V4L1
|
||||
|
||||
protected:
|
||||
std::string device;
|
||||
int channel;
|
||||
int standard;
|
||||
int palette;
|
||||
bool device_prime;
|
||||
bool channel_prime;
|
||||
int channel_index;
|
||||
unsigned int extras;
|
||||
|
||||
unsigned int conversion_type; /* 0 = no conversion needed, 1 = use libswscale, 2 = zm internal conversion, 3 = jpeg decoding */
|
||||
convert_fptr_t conversion_fptr; /* Pointer to conversion function used */
|
||||
|
||||
uint32_t AutoSelectFormat(int p_colours);
|
||||
std::string device;
|
||||
int channel;
|
||||
int standard;
|
||||
int palette;
|
||||
bool device_prime;
|
||||
bool channel_prime;
|
||||
int channel_index;
|
||||
unsigned int extras;
|
||||
|
||||
unsigned int conversion_type; /* 0 = no conversion needed, 1 = use libswscale, 2 = zm internal conversion, 3 = jpeg decoding */
|
||||
convert_fptr_t conversion_fptr; /* Pointer to conversion function used */
|
||||
|
||||
uint32_t AutoSelectFormat(int p_colours);
|
||||
|
||||
static int camera_count;
|
||||
static int channel_count;
|
||||
static int channels[VIDEO_MAX_FRAME];
|
||||
static int standards[VIDEO_MAX_FRAME];
|
||||
static int vid_fd;
|
||||
static int v4l_version;
|
||||
bool v4l_multi_buffer;
|
||||
unsigned int v4l_captures_per_frame;
|
||||
static int camera_count;
|
||||
static int channel_count;
|
||||
static int channels[VIDEO_MAX_FRAME];
|
||||
static int standards[VIDEO_MAX_FRAME];
|
||||
static int vid_fd;
|
||||
static int v4l_version;
|
||||
bool v4l_multi_buffer;
|
||||
unsigned int v4l_captures_per_frame;
|
||||
|
||||
#if ZM_HAS_V4L2
|
||||
static V4L2Data v4l2_data;
|
||||
static V4L2Data v4l2_data;
|
||||
#endif // ZM_HAS_V4L2
|
||||
#if ZM_HAS_V4L1
|
||||
static V4L1Data v4l1_data;
|
||||
static V4L1Data v4l1_data;
|
||||
#endif // ZM_HAS_V4L1
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
static AVFrame **capturePictures;
|
||||
_AVPIXELFORMAT imagePixFormat;
|
||||
_AVPIXELFORMAT capturePixFormat;
|
||||
struct SwsContext *imgConversionContext;
|
||||
AVFrame *tmpPicture;
|
||||
static AVFrame **capturePictures;
|
||||
_AVPIXELFORMAT imagePixFormat;
|
||||
_AVPIXELFORMAT capturePixFormat;
|
||||
struct SwsContext *imgConversionContext;
|
||||
AVFrame *tmpPicture;
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
|
||||
static LocalCamera *last_camera;
|
||||
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();
|
||||
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();
|
||||
void Terminate();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
||||
const std::string &Device() const { return( device ); }
|
||||
const std::string &Device() const { return( device ); }
|
||||
|
||||
int Channel() const { return( channel ); }
|
||||
int Standard() const { return( standard ); }
|
||||
int Palette() const { return( palette ); }
|
||||
int Extras() const { return( extras ); }
|
||||
int Channel() const { return( channel ); }
|
||||
int Standard() const { return( standard ); }
|
||||
int Palette() const { return( palette ); }
|
||||
int Extras() const { return( extras ); }
|
||||
|
||||
int Brightness( int p_brightness=-1 );
|
||||
int Hue( int p_hue=-1 );
|
||||
int Colour( int p_colour=-1 );
|
||||
int Contrast( int p_contrast=-1 );
|
||||
int Brightness( int p_brightness=-1 );
|
||||
int Hue( int p_hue=-1 );
|
||||
int Colour( int p_colour=-1 );
|
||||
int Contrast( int p_contrast=-1 );
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int PrimeCapture();
|
||||
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 );
|
||||
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
|
||||
};
|
||||
|
||||
#endif // ZM_HAS_V4L
|
||||
|
|
File diff suppressed because it is too large
Load Diff
1390
src/zm_monitor.cpp
1390
src/zm_monitor.cpp
File diff suppressed because it is too large
Load Diff
683
src/zm_monitor.h
683
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;
|
||||
|
@ -97,43 +97,43 @@ protected:
|
|||
/* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t size; /* +0 */
|
||||
uint32_t last_write_index; /* +4 */
|
||||
uint32_t last_read_index; /* +8 */
|
||||
uint32_t state; /* +12 */
|
||||
uint32_t last_event; /* +16 */
|
||||
uint32_t action; /* +20 */
|
||||
int32_t brightness; /* +24 */
|
||||
int32_t hue; /* +28 */
|
||||
int32_t colour; /* +32 */
|
||||
int32_t contrast; /* +36 */
|
||||
int32_t alarm_x; /* +40 */
|
||||
int32_t alarm_y; /* +44 */
|
||||
uint8_t valid; /* +48 */
|
||||
uint8_t active; /* +49 */
|
||||
uint8_t signal; /* +50 */
|
||||
uint8_t format; /* +51 */
|
||||
uint32_t imagesize; /* +52 */
|
||||
uint32_t epadding1; /* +56 */
|
||||
uint32_t epadding2; /* +60 */
|
||||
uint32_t size; /* +0 */
|
||||
uint32_t last_write_index; /* +4 */
|
||||
uint32_t last_read_index; /* +8 */
|
||||
uint32_t state; /* +12 */
|
||||
uint32_t last_event; /* +16 */
|
||||
uint32_t action; /* +20 */
|
||||
int32_t brightness; /* +24 */
|
||||
int32_t hue; /* +28 */
|
||||
int32_t colour; /* +32 */
|
||||
int32_t contrast; /* +36 */
|
||||
int32_t alarm_x; /* +40 */
|
||||
int32_t alarm_y; /* +44 */
|
||||
uint8_t valid; /* +48 */
|
||||
uint8_t active; /* +49 */
|
||||
uint8_t signal; /* +50 */
|
||||
uint8_t format; /* +51 */
|
||||
uint32_t imagesize; /* +52 */
|
||||
uint32_t epadding1; /* +56 */
|
||||
uint32_t epadding2; /* +60 */
|
||||
/*
|
||||
** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
|
||||
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
|
||||
*/
|
||||
union { /* +64 */
|
||||
time_t last_write_time;
|
||||
uint64_t extrapad1;
|
||||
** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
|
||||
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
|
||||
*/
|
||||
union { /* +64 */
|
||||
time_t last_write_time;
|
||||
uint64_t extrapad1;
|
||||
};
|
||||
union { /* +72 */
|
||||
time_t last_read_time;
|
||||
uint64_t extrapad2;
|
||||
union { /* +72 */
|
||||
time_t last_read_time;
|
||||
uint64_t extrapad2;
|
||||
};
|
||||
uint8_t control_state[256]; /* +80 */
|
||||
|
||||
uint8_t control_state[256]; /* +80 */
|
||||
|
||||
} SharedData;
|
||||
|
||||
typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState;
|
||||
|
||||
|
||||
/* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */
|
||||
typedef struct
|
||||
{
|
||||
|
@ -149,406 +149,283 @@ protected:
|
|||
/* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */
|
||||
struct Snapshot
|
||||
{
|
||||
struct timeval *timestamp;
|
||||
Image *image;
|
||||
struct timeval *timestamp;
|
||||
Image *image;
|
||||
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
|
||||
{
|
||||
protected:
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
uint32_t size;
|
||||
char event_file[4096];
|
||||
uint32_t recording; //bool arch dependent so use uint32 instead
|
||||
//uint32_t frameNumber;
|
||||
} VideoStoreData;
|
||||
|
||||
bool connected;
|
||||
time_t last_connect_time;
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
||||
class MonitorLink {
|
||||
protected:
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
|
||||
bool connected;
|
||||
time_t last_connect_time;
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
int map_fd;
|
||||
char mem_file[PATH_MAX];
|
||||
int map_fd;
|
||||
char mem_file[PATH_MAX];
|
||||
#else // ZM_MEM_MAPPED
|
||||
int shm_id;
|
||||
int shm_id;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
<<<<<<< HEAD
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
off_t mem_size;
|
||||
unsigned char *mem_ptr;
|
||||
|
||||
volatile SharedData *shared_data;
|
||||
volatile TriggerData *trigger_data;
|
||||
volatile SharedData *shared_data;
|
||||
volatile TriggerData *trigger_data;
|
||||
volatile VideoStoreData *video_store_data;
|
||||
|
||||
int last_state;
|
||||
int last_event;
|
||||
|
||||
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 );
|
||||
}
|
||||
public:
|
||||
MonitorLink( int p_id, const char *p_name );
|
||||
~MonitorLink();
|
||||
|
||||
inline bool isConnected() const
|
||||
{
|
||||
return( connected );
|
||||
}
|
||||
inline time_t getLastConnectTime() const
|
||||
{
|
||||
return( last_connect_time );
|
||||
}
|
||||
inline int Id() const {
|
||||
return( id );
|
||||
}
|
||||
inline const char *Name() const {
|
||||
return( name );
|
||||
}
|
||||
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
inline bool isConnected() const {
|
||||
return( connected );
|
||||
}
|
||||
inline time_t getLastConnectTime() const {
|
||||
return( last_connect_time );
|
||||
}
|
||||
|
||||
bool isAlarmed();
|
||||
bool inAlarm();
|
||||
bool hasAlarmed();
|
||||
};
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
|
||||
protected:
|
||||
// These are read from the DB and thereafter remain unchanged
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
unsigned int server_id; // Id of the Server object
|
||||
unsigned int storage_id; // Id of the Storage Object, which currently will just provide a path, but in future may do more.
|
||||
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;
|
||||
=======
|
||||
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();
|
||||
bool isAlarmed();
|
||||
bool inAlarm();
|
||||
bool hasAlarmed();
|
||||
};
|
||||
|
||||
protected:
|
||||
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
|
||||
unsigned int id;
|
||||
char name[64];
|
||||
unsigned int server_id; // Id of the Server object
|
||||
unsigned int storage_id; // Id of the Storage Object, which currently will just provide a path, but in future may do more.
|
||||
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;
|
||||
|
||||
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
|
||||
int savejpegspref;
|
||||
int videowriterpref;
|
||||
std::string encoderparams;
|
||||
std::vector<EncoderParameter_t> encoderparamsvec;
|
||||
bool record_audio; // Whether to store the audio that we receive
|
||||
|
||||
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;
|
||||
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;
|
||||
char mem_file[PATH_MAX];
|
||||
int map_fd;
|
||||
char mem_file[PATH_MAX];
|
||||
#else // ZM_MEM_MAPPED
|
||||
int shm_id;
|
||||
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
|
||||
VideoStoreData *video_store_data;
|
||||
|
||||
SharedData *shared_data;
|
||||
TriggerData *trigger_data;
|
||||
Snapshot *image_buffer;
|
||||
Snapshot next_buffer; /* Used by four field deinterlacing */
|
||||
Snapshot *pre_event_buffer;
|
||||
|
||||
Snapshot *image_buffer;
|
||||
Snapshot next_buffer; /* Used by four field deinterlacing */
|
||||
Snapshot *pre_event_buffer;
|
||||
Camera *camera;
|
||||
|
||||
Camera *camera;
|
||||
Event *event;
|
||||
|
||||
Event *event;
|
||||
int n_zones;
|
||||
Zone **zones;
|
||||
|
||||
int n_zones;
|
||||
Zone **zones;
|
||||
struct timeval **timestamps;
|
||||
Image **images;
|
||||
|
||||
struct timeval **timestamps;
|
||||
Image **images;
|
||||
const unsigned char *privacy_bitmask;
|
||||
|
||||
const unsigned char *privacy_bitmask;
|
||||
int n_linked_monitors;
|
||||
MonitorLink **linked_monitors;
|
||||
|
||||
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 );
|
||||
~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 Storage *getStorage()
|
||||
{
|
||||
if ( ! storage ) {
|
||||
storage = new Storage( storage_id );
|
||||
}
|
||||
return( storage );
|
||||
}
|
||||
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() ); }
|
||||
|
||||
=======
|
||||
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 );
|
||||
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 Function GetFunction() const
|
||||
{
|
||||
inline Storage *getStorage() {
|
||||
if ( ! storage ) {
|
||||
storage = new Storage( storage_id );
|
||||
}
|
||||
return( storage );
|
||||
}
|
||||
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() ); }
|
||||
|
||||
>>>>>>> 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 );
|
||||
struct timeval GetTimestamp( int index=-1 ) const;
|
||||
|
@ -577,23 +454,20 @@ 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() );
|
||||
}
|
||||
|
||||
unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet );
|
||||
// DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info.
|
||||
//unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet );
|
||||
// DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info.
|
||||
//unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet );
|
||||
bool CheckSignal( const Image *image );
|
||||
bool Analyse();
|
||||
void DumpImage( Image *dump_image ) const;
|
||||
|
@ -630,54 +504,49 @@ public:
|
|||
|
||||
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
|
||||
|
||||
class MonitorStream : public StreamBase
|
||||
{
|
||||
protected:
|
||||
typedef struct SwapImage {
|
||||
bool valid;
|
||||
struct timeval timestamp;
|
||||
char file_name[PATH_MAX];
|
||||
} SwapImage;
|
||||
class MonitorStream : public StreamBase {
|
||||
protected:
|
||||
typedef struct SwapImage {
|
||||
bool valid;
|
||||
struct timeval timestamp;
|
||||
char file_name[PATH_MAX];
|
||||
} SwapImage;
|
||||
|
||||
private:
|
||||
SwapImage *temp_image_buffer;
|
||||
int temp_image_buffer_count;
|
||||
int temp_read_index;
|
||||
int temp_write_index;
|
||||
private:
|
||||
SwapImage *temp_image_buffer;
|
||||
int temp_image_buffer_count;
|
||||
int temp_read_index;
|
||||
int temp_write_index;
|
||||
|
||||
protected:
|
||||
time_t ttl;
|
||||
protected:
|
||||
time_t ttl;
|
||||
|
||||
protected:
|
||||
int playback_buffer;
|
||||
bool delayed;
|
||||
protected:
|
||||
int playback_buffer;
|
||||
bool delayed;
|
||||
|
||||
int frame_count;
|
||||
int frame_count;
|
||||
|
||||
protected:
|
||||
bool checkSwapPath( const char *path, bool create_path );
|
||||
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 );
|
||||
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 )
|
||||
{
|
||||
}
|
||||
void setStreamBuffer( int p_playback_buffer )
|
||||
{
|
||||
playback_buffer = p_playback_buffer;
|
||||
}
|
||||
void setStreamTTL( time_t p_ttl )
|
||||
{
|
||||
ttl = p_ttl;
|
||||
}
|
||||
bool setStreamStart( int monitor_id )
|
||||
{
|
||||
return loadMonitor( monitor_id );
|
||||
}
|
||||
void runStream();
|
||||
public:
|
||||
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) {
|
||||
}
|
||||
void setStreamBuffer( int p_playback_buffer ) {
|
||||
playback_buffer = p_playback_buffer;
|
||||
}
|
||||
void setStreamTTL( time_t p_ttl ) {
|
||||
ttl = p_ttl;
|
||||
}
|
||||
bool setStreamStart( int monitor_id ) {
|
||||
return loadMonitor( monitor_id );
|
||||
}
|
||||
void runStream();
|
||||
};
|
||||
|
||||
#endif // ZM_MONITOR_H
|
||||
|
|
1208
src/zm_mpeg.cpp
1208
src/zm_mpeg.cpp
File diff suppressed because it is too large
Load Diff
|
@ -21,67 +21,82 @@
|
|||
|
||||
#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 ),
|
||||
protocol( p_protocol ),
|
||||
host( p_host ),
|
||||
port( p_port ),
|
||||
path( p_path ),
|
||||
hp( 0 )
|
||||
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 ),
|
||||
path( p_path ),
|
||||
hp( 0 )
|
||||
{
|
||||
if ( path[0] != '/' )
|
||||
path = '/'+path;
|
||||
if ( path[0] != '/' )
|
||||
path = '/'+path;
|
||||
}
|
||||
|
||||
RemoteCamera::~RemoteCamera()
|
||||
{
|
||||
if(hp != NULL) {
|
||||
freeaddrinfo(hp);
|
||||
hp = NULL;
|
||||
}
|
||||
if(hp != NULL) {
|
||||
freeaddrinfo(hp);
|
||||
hp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteCamera::Initialise()
|
||||
{
|
||||
if( protocol.empty() )
|
||||
Fatal( "No protocol specified for remote camera" );
|
||||
if( protocol.empty() )
|
||||
Fatal( "No protocol specified for remote camera" );
|
||||
|
||||
if( host.empty() )
|
||||
Fatal( "No host specified for remote camera" );
|
||||
if( host.empty() )
|
||||
Fatal( "No host specified for remote camera" );
|
||||
|
||||
if( port.empty() )
|
||||
Fatal( "No port specified for remote camera" );
|
||||
if( port.empty() )
|
||||
Fatal( "No port specified for remote camera" );
|
||||
|
||||
//if( path.empty() )
|
||||
//Fatal( "No path specified for remote camera" );
|
||||
//if( path.empty() )
|
||||
//Fatal( "No path specified for remote camera" );
|
||||
|
||||
// Cache as much as we can to speed things up
|
||||
std::string::size_type authIndex = host.rfind( '@' );
|
||||
// Cache as much as we can to speed things up
|
||||
std::string::size_type authIndex = host.rfind( '@' );
|
||||
|
||||
if ( authIndex != std::string::npos )
|
||||
{
|
||||
auth = host.substr( 0, authIndex );
|
||||
host.erase( 0, authIndex+1 );
|
||||
auth64 = base64Encode( auth );
|
||||
if ( authIndex != std::string::npos )
|
||||
{
|
||||
auth = host.substr( 0, authIndex );
|
||||
host.erase( 0, authIndex+1 );
|
||||
auth64 = base64Encode( auth );
|
||||
|
||||
authIndex = auth.rfind( ':' );
|
||||
username = auth.substr(0,authIndex);
|
||||
password = auth.substr( authIndex+1, auth.length() );
|
||||
authIndex = auth.rfind( ':' );
|
||||
username = auth.substr(0,authIndex);
|
||||
password = auth.substr( authIndex+1, auth.length() );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mNeedAuth = false;
|
||||
mAuthenticator = new zm::Authenticator(username,password);
|
||||
mNeedAuth = false;
|
||||
mAuthenticator = new zm::Authenticator(username,password);
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp);
|
||||
if ( ret != 0 )
|
||||
{
|
||||
Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) );
|
||||
}
|
||||
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp);
|
||||
if ( ret != 0 )
|
||||
{
|
||||
Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -35,44 +36,60 @@
|
|||
class RemoteCamera : public Camera
|
||||
{
|
||||
protected:
|
||||
std::string protocol;
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string path;
|
||||
std::string auth;
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string auth64;
|
||||
std::string protocol;
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string path;
|
||||
std::string auth;
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string auth64;
|
||||
|
||||
// Reworked authentication system
|
||||
// First try without authentication, even if we have a username and password
|
||||
// on receiving a 401 response, select authentication method (basic or digest)
|
||||
// fill required fields and set needAuth
|
||||
// subsequent requests can set the required authentication header.
|
||||
bool mNeedAuth;
|
||||
zm::Authenticator* mAuthenticator;
|
||||
// Reworked authentication system
|
||||
// First try without authentication, even if we have a username and password
|
||||
// on receiving a 401 response, select authentication method (basic or digest)
|
||||
// fill required fields and set needAuth
|
||||
// subsequent requests can set the required authentication header.
|
||||
bool mNeedAuth;
|
||||
zm::Authenticator* mAuthenticator;
|
||||
protected:
|
||||
struct addrinfo *hp;
|
||||
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 );
|
||||
virtual ~RemoteCamera();
|
||||
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 ); }
|
||||
const std::string &Host() const { return( host ); }
|
||||
const std::string &Port() const { return( port ); }
|
||||
const std::string &Path() const { return( path ); }
|
||||
const std::string &Auth() const { return( auth ); }
|
||||
const std::string &Username() const { return( username ); }
|
||||
const std::string &Password() const { return( password ); }
|
||||
const std::string &Protocol() const { return( protocol ); }
|
||||
const std::string &Host() const { return( host ); }
|
||||
const std::string &Port() const { return( port ); }
|
||||
const std::string &Path() const { return( path ); }
|
||||
const std::string &Auth() const { return( auth ); }
|
||||
const std::string &Username() const { return( username ); }
|
||||
const std::string &Password() const { return( password ); }
|
||||
|
||||
virtual void Initialise();
|
||||
virtual void Terminate() = 0;
|
||||
virtual int Connect() = 0;
|
||||
virtual int Disconnect() = 0;
|
||||
virtual int PreCapture() = 0;
|
||||
virtual int Capture( Image &image ) = 0;
|
||||
virtual int PostCapture() = 0;
|
||||
virtual void Initialise();
|
||||
virtual void Terminate() = 0;
|
||||
virtual int Connect() = 0;
|
||||
virtual int Disconnect() = 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_REMOTE_CAMERA_H
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,31 +33,32 @@
|
|||
class RemoteCameraHttp : public RemoteCamera
|
||||
{
|
||||
protected:
|
||||
std::string request;
|
||||
struct timeval timeout;
|
||||
//struct hostent *hp;
|
||||
//struct sockaddr_in sa;
|
||||
int sd;
|
||||
Buffer buffer;
|
||||
enum { SINGLE_IMAGE, MULTI_IMAGE } mode;
|
||||
enum { UNDEF, JPEG, X_RGB, X_RGBZ } format;
|
||||
enum { HEADER, HEADERCONT, SUBHEADER, SUBHEADERCONT, CONTENT } state;
|
||||
enum { SIMPLE, REGEXP } method;
|
||||
std::string request;
|
||||
struct timeval timeout;
|
||||
//struct hostent *hp;
|
||||
//struct sockaddr_in sa;
|
||||
int sd;
|
||||
Buffer buffer;
|
||||
enum { SINGLE_IMAGE, MULTI_IMAGE } mode;
|
||||
enum { UNDEF, JPEG, X_RGB, X_RGBZ } format;
|
||||
enum { HEADER, HEADERCONT, SUBHEADER, SUBHEADERCONT, CONTENT } state;
|
||||
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();
|
||||
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();
|
||||
void Terminate() { Disconnect(); }
|
||||
int Connect();
|
||||
int Disconnect();
|
||||
int SendRequest();
|
||||
int ReadData( Buffer &buffer, int bytes_expected=0 );
|
||||
int GetResponse();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
void Initialise();
|
||||
void Terminate() { Disconnect(); }
|
||||
int Connect();
|
||||
int Disconnect();
|
||||
int SendRequest();
|
||||
int ReadData( Buffer &buffer, int bytes_expected=0 );
|
||||
int GetResponse();
|
||||
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,344 +28,566 @@
|
|||
#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 ),
|
||||
rtsp_describe( p_rtsp_describe ),
|
||||
rtspThread( 0 )
|
||||
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 )
|
||||
|
||||
{
|
||||
if ( p_method == "rtpUni" )
|
||||
method = RtspThread::RTP_UNICAST;
|
||||
else if ( p_method == "rtpMulti" )
|
||||
method = RtspThread::RTP_MULTICAST;
|
||||
else if ( p_method == "rtpRtsp" )
|
||||
method = RtspThread::RTP_RTSP;
|
||||
else if ( p_method == "rtpRtspHttp" )
|
||||
method = RtspThread::RTP_RTSP_HTTP;
|
||||
else
|
||||
Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), id );
|
||||
if ( p_method == "rtpUni" )
|
||||
method = RtspThread::RTP_UNICAST;
|
||||
else if ( p_method == "rtpMulti" )
|
||||
method = RtspThread::RTP_MULTICAST;
|
||||
else if ( p_method == "rtpRtsp" )
|
||||
method = RtspThread::RTP_RTSP;
|
||||
else if ( p_method == "rtpRtspHttp" )
|
||||
method = RtspThread::RTP_RTSP_HTTP;
|
||||
else
|
||||
Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), id );
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
mFormatContext = NULL;
|
||||
mVideoStreamId = -1;
|
||||
mCodecContext = NULL;
|
||||
mCodec = NULL;
|
||||
mRawFrame = NULL;
|
||||
mFrame = NULL;
|
||||
frameCount = 0;
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
mConvertContext = NULL;
|
||||
if ( capture )
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
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;
|
||||
#endif
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
||||
imagePixFormat = AV_PIX_FMT_RGBA;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
||||
imagePixFormat = AV_PIX_FMT_RGB24;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
imagePixFormat = AV_PIX_FMT_GRAY8;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||
if(colours == ZM_COLOUR_RGB32) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
||||
imagePixFormat = AV_PIX_FMT_RGBA;
|
||||
} else if(colours == ZM_COLOUR_RGB24) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_RGB;
|
||||
imagePixFormat = AV_PIX_FMT_RGB24;
|
||||
} else if(colours == ZM_COLOUR_GRAY8) {
|
||||
subpixelorder = ZM_SUBPIX_ORDER_NONE;
|
||||
imagePixFormat = AV_PIX_FMT_GRAY8;
|
||||
} else {
|
||||
Panic("Unexpected colours: %d",colours);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RemoteCameraRtsp::~RemoteCameraRtsp()
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
av_frame_free( &mFrame );
|
||||
av_frame_free( &mRawFrame );
|
||||
av_frame_free( &mFrame );
|
||||
av_frame_free( &mRawFrame );
|
||||
#else
|
||||
av_freep( &mFrame );
|
||||
av_freep( &mRawFrame );
|
||||
av_freep( &mFrame );
|
||||
av_freep( &mRawFrame );
|
||||
#endif
|
||||
|
||||
|
||||
#if HAVE_LIBSWSCALE
|
||||
if ( mConvertContext )
|
||||
{
|
||||
sws_freeContext( mConvertContext );
|
||||
mConvertContext = NULL;
|
||||
}
|
||||
if ( mConvertContext )
|
||||
{
|
||||
sws_freeContext( mConvertContext );
|
||||
mConvertContext = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( mCodecContext )
|
||||
{
|
||||
avcodec_close( mCodecContext );
|
||||
mCodecContext = NULL; // Freed by avformat_free_context in the destructor of RtspThread class
|
||||
}
|
||||
if ( mCodecContext )
|
||||
{
|
||||
avcodec_close( mCodecContext );
|
||||
mCodecContext = NULL; // Freed by avformat_free_context in the destructor of RtspThread class
|
||||
}
|
||||
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
if ( capture )
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteCameraRtsp::Initialise()
|
||||
{
|
||||
RemoteCamera::Initialise();
|
||||
RemoteCamera::Initialise();
|
||||
|
||||
int max_size = width*height*colours;
|
||||
int max_size = width*height*colours;
|
||||
|
||||
buffer.size( max_size );
|
||||
// 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() )
|
||||
av_log_set_level( AV_LOG_DEBUG );
|
||||
else
|
||||
av_log_set_level( AV_LOG_QUIET );
|
||||
if ( logDebugging() )
|
||||
av_log_set_level( AV_LOG_DEBUG );
|
||||
else
|
||||
av_log_set_level( AV_LOG_QUIET );
|
||||
|
||||
av_register_all();
|
||||
av_register_all();
|
||||
|
||||
Connect();
|
||||
Connect();
|
||||
}
|
||||
|
||||
void RemoteCameraRtsp::Terminate()
|
||||
{
|
||||
Disconnect();
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::Connect()
|
||||
{
|
||||
rtspThread = new RtspThread( id, method, protocol, host, port, path, auth, rtsp_describe );
|
||||
rtspThread = new RtspThread( id, method, protocol, host, port, path, auth, rtsp_describe );
|
||||
|
||||
rtspThread->start();
|
||||
rtspThread->start();
|
||||
|
||||
return( 0 );
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::Disconnect()
|
||||
{
|
||||
if ( rtspThread )
|
||||
{
|
||||
rtspThread->stop();
|
||||
rtspThread->join();
|
||||
delete rtspThread;
|
||||
rtspThread = 0;
|
||||
}
|
||||
return( 0 );
|
||||
if ( rtspThread )
|
||||
{
|
||||
rtspThread->stop();
|
||||
rtspThread->join();
|
||||
delete rtspThread;
|
||||
rtspThread = 0;
|
||||
}
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::PrimeCapture()
|
||||
{
|
||||
Debug( 2, "Waiting for sources" );
|
||||
for ( int i = 0; i < 100 && !rtspThread->hasSources(); i++ )
|
||||
{
|
||||
usleep( 100000 );
|
||||
}
|
||||
if ( !rtspThread->hasSources() )
|
||||
Fatal( "No RTSP sources" );
|
||||
Debug( 2, "Waiting for sources" );
|
||||
for ( int i = 0; i < 100 && !rtspThread->hasSources(); i++ )
|
||||
{
|
||||
usleep( 100000 );
|
||||
}
|
||||
if ( !rtspThread->hasSources() )
|
||||
Fatal( "No RTSP sources" );
|
||||
|
||||
Debug( 2, "Got sources" );
|
||||
Debug( 2, "Got sources" );
|
||||
|
||||
mFormatContext = rtspThread->getFormatContext();
|
||||
mFormatContext = rtspThread->getFormatContext();
|
||||
|
||||
// Find first video stream present
|
||||
mVideoStreamId = -1;
|
||||
|
||||
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ )
|
||||
// Find first video stream present
|
||||
mVideoStreamId = -1;
|
||||
mAudioStreamId = -1;
|
||||
|
||||
// Find the first video stream.
|
||||
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
|
||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
|
||||
#else
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
||||
#endif
|
||||
{
|
||||
mVideoStreamId = i;
|
||||
break;
|
||||
{
|
||||
if ( mVideoStreamId == -1 ) {
|
||||
mVideoStreamId = i;
|
||||
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 ( 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;
|
||||
// Get a pointer to the codec context for the video stream
|
||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||
|
||||
// Find the decoder for the video stream
|
||||
mCodec = avcodec_find_decoder( mCodecContext->codec_id );
|
||||
if ( mCodec == NULL )
|
||||
Panic( "Unable to locate codec %d decoder", mCodecContext->codec_id );
|
||||
// Find the decoder for the video stream
|
||||
mCodec = avcodec_find_decoder( mCodecContext->codec_id );
|
||||
if ( mCodec == NULL )
|
||||
Panic( "Unable to locate codec %d decoder", mCodecContext->codec_id );
|
||||
|
||||
// Open codec
|
||||
// Open codec
|
||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||
if ( avcodec_open( mCodecContext, mCodec ) < 0 )
|
||||
if ( avcodec_open( mCodecContext, mCodec ) < 0 )
|
||||
#else
|
||||
if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 )
|
||||
if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 )
|
||||
#endif
|
||||
Panic( "Can't open codec" );
|
||||
Panic( "Can't open codec" );
|
||||
|
||||
// Allocate space for the native video frame
|
||||
// Allocate space for the native video frame
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
mRawFrame = av_frame_alloc();
|
||||
mRawFrame = av_frame_alloc();
|
||||
#else
|
||||
mRawFrame = avcodec_alloc_frame();
|
||||
mRawFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
|
||||
// Allocate space for the converted video frame
|
||||
// Allocate space for the converted video frame
|
||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||
mFrame = av_frame_alloc();
|
||||
mFrame = av_frame_alloc();
|
||||
#else
|
||||
mFrame = avcodec_alloc_frame();
|
||||
mFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
|
||||
if(mRawFrame == NULL || mFrame == NULL)
|
||||
Fatal( "Unable to allocate frame(s)");
|
||||
|
||||
int pSize = avpicture_get_size( imagePixFormat, width, height );
|
||||
if( (unsigned int)pSize != imagesize) {
|
||||
Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize);
|
||||
}
|
||||
/*
|
||||
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);
|
||||
}
|
||||
/*
|
||||
#if HAVE_LIBSWSCALE
|
||||
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
|
||||
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
|
||||
}
|
||||
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
|
||||
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
|
||||
}
|
||||
|
||||
if(!sws_isSupportedOutput(imagePixFormat)) {
|
||||
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
||||
}
|
||||
|
||||
if(!sws_isSupportedOutput(imagePixFormat)) {
|
||||
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
||||
}
|
||||
|
||||
#else // HAVE_LIBSWSCALE
|
||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
*/
|
||||
|
||||
return( 0 );
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::PreCapture()
|
||||
{
|
||||
if ( !rtspThread->isRunning() )
|
||||
return( -1 );
|
||||
if ( !rtspThread->hasSources() )
|
||||
{
|
||||
Error( "Cannot precapture, no RTP sources" );
|
||||
return( -1 );
|
||||
}
|
||||
return( 0 );
|
||||
int RemoteCameraRtsp::PreCapture() {
|
||||
if ( !rtspThread->isRunning() )
|
||||
return( -1 );
|
||||
if ( !rtspThread->hasSources() )
|
||||
{
|
||||
Error( "Cannot precapture, no RTP sources" );
|
||||
return( -1 );
|
||||
}
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int RemoteCameraRtsp::Capture( Image &image )
|
||||
{
|
||||
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);
|
||||
int RemoteCameraRtsp::Capture( Image &image ) {
|
||||
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 ( 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 ( !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;
|
||||
}
|
||||
}
|
||||
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 The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures
|
||||
if(nalType == 7)
|
||||
{
|
||||
lastSps = buffer;
|
||||
continue;
|
||||
}
|
||||
// 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;
|
||||
continue;
|
||||
}
|
||||
// IDR
|
||||
else if(nalType == 5)
|
||||
{
|
||||
buffer += lastSps;
|
||||
buffer += lastPps;
|
||||
}
|
||||
} else {
|
||||
Debug(3, "Not an h264 packet");
|
||||
}
|
||||
|
||||
av_init_packet( &packet );
|
||||
|
||||
while ( !frameComplete && buffer.size() > 0 )
|
||||
{
|
||||
packet.data = buffer.head();
|
||||
packet.size = buffer.size();
|
||||
av_init_packet( &packet );
|
||||
|
||||
while ( !frameComplete && buffer.size() > 0 ) {
|
||||
packet.data = buffer.head();
|
||||
packet.size = buffer.size();
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
// 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 );
|
||||
|
||||
#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 );
|
||||
int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet );
|
||||
#else
|
||||
int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size );
|
||||
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;
|
||||
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 ( frameComplete ) {
|
||||
|
||||
Debug( 3, "Got frame %d", frameCount );
|
||||
|
||||
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 );
|
||||
// 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 );
|
||||
|
||||
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 );
|
||||
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" );
|
||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
||||
#endif // HAVE_LIBSWSCALE
|
||||
|
||||
frameCount++;
|
||||
|
||||
} /* frame complete */
|
||||
|
||||
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);
|
||||
av_packet_unref( &packet );
|
||||
#else
|
||||
av_free_packet( &packet );
|
||||
av_free_packet( &packet );
|
||||
#endif
|
||||
} /* getFrame() */
|
||||
|
||||
if(frameComplete)
|
||||
return (0);
|
||||
|
||||
}
|
||||
return (0) ;
|
||||
}
|
||||
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 );
|
||||
#else
|
||||
av_free_packet( &packet );
|
||||
#endif
|
||||
} /* getFrame() */
|
||||
|
||||
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()
|
||||
{
|
||||
return( 0 );
|
||||
return( 0 );
|
||||
}
|
||||
#endif // HAVE_LIBAVFORMAT
|
||||
|
|
|
@ -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
|
||||
|
@ -35,51 +36,56 @@
|
|||
class RemoteCameraRtsp : public RemoteCamera
|
||||
{
|
||||
protected:
|
||||
struct sockaddr_in rtsp_sa;
|
||||
struct sockaddr_in rtcp_sa;
|
||||
int rtsp_sd;
|
||||
int rtp_sd;
|
||||
int rtcp_sd;
|
||||
bool rtsp_describe;
|
||||
struct sockaddr_in rtsp_sa;
|
||||
struct sockaddr_in rtcp_sa;
|
||||
int rtsp_sd;
|
||||
int rtp_sd;
|
||||
int rtcp_sd;
|
||||
bool rtsp_describe;
|
||||
|
||||
Buffer buffer;
|
||||
Buffer lastSps;
|
||||
Buffer lastPps;
|
||||
Buffer buffer;
|
||||
Buffer lastSps;
|
||||
Buffer lastPps;
|
||||
|
||||
RtspThread::RtspMethod method;
|
||||
RtspThread::RtspMethod method;
|
||||
|
||||
RtspThread *rtspThread;
|
||||
RtspThread *rtspThread;
|
||||
|
||||
int frameCount;
|
||||
|
||||
int frameCount;
|
||||
|
||||
#if HAVE_LIBAVFORMAT
|
||||
AVFormatContext *mFormatContext;
|
||||
int mVideoStreamId;
|
||||
AVCodecContext *mCodecContext;
|
||||
AVCodec *mCodec;
|
||||
AVFrame *mRawFrame;
|
||||
AVFrame *mFrame;
|
||||
_AVPIXELFORMAT imagePixFormat;
|
||||
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;
|
||||
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();
|
||||
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();
|
||||
void Terminate();
|
||||
int Connect();
|
||||
int Disconnect();
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
int Connect();
|
||||
int Disconnect();
|
||||
|
||||
int PrimeCapture();
|
||||
int PreCapture();
|
||||
int Capture( Image &image );
|
||||
int PostCapture();
|
||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
||||
};
|
||||
|
||||
#endif // ZM_REMOTE_CAMERA_RTSP_H
|
||||
|
|
|
@ -27,358 +27,374 @@
|
|||
#if HAVE_LIBAVCODEC
|
||||
|
||||
RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, const std::string &remoteHost, int remotePortBase, uint32_t ssrc, uint16_t seq, uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId ) :
|
||||
mId( id ),
|
||||
mSsrc( ssrc ),
|
||||
mLocalHost( localHost ),
|
||||
mRemoteHost( remoteHost ),
|
||||
mRtpClock( rtpClock ),
|
||||
mCodecId( codecId ),
|
||||
mFrame( 65536 ),
|
||||
mFrameCount( 0 ),
|
||||
mFrameGood( true ),
|
||||
mFrameReady( false ),
|
||||
mFrameProcessed( false )
|
||||
mId( id ),
|
||||
mSsrc( ssrc ),
|
||||
mLocalHost( localHost ),
|
||||
mRemoteHost( remoteHost ),
|
||||
mRtpClock( rtpClock ),
|
||||
mCodecId( codecId ),
|
||||
mFrame( 65536 ),
|
||||
mFrameCount( 0 ),
|
||||
mFrameGood( true ),
|
||||
mFrameReady( false ),
|
||||
mFrameProcessed( false )
|
||||
{
|
||||
char hostname[256] = "";
|
||||
gethostname( hostname, sizeof(hostname) );
|
||||
char hostname[256] = "";
|
||||
gethostname( hostname, sizeof(hostname) );
|
||||
|
||||
mCname = stringtf( "zm-%d@%s", mId, hostname );
|
||||
Debug( 3, "RTP CName = %s", mCname.c_str() );
|
||||
mCname = stringtf( "zm-%d@%s", mId, hostname );
|
||||
Debug( 3, "RTP CName = %s", mCname.c_str() );
|
||||
|
||||
init( seq );
|
||||
mMaxSeq = seq - 1;
|
||||
mProbation = MIN_SEQUENTIAL;
|
||||
init( seq );
|
||||
mMaxSeq = seq - 1;
|
||||
mProbation = MIN_SEQUENTIAL;
|
||||
|
||||
mLocalPortChans[0] = localPortBase;
|
||||
mLocalPortChans[1] = localPortBase+1;
|
||||
mLocalPortChans[0] = localPortBase;
|
||||
mLocalPortChans[1] = localPortBase+1;
|
||||
|
||||
mRemotePortChans[0] = remotePortBase;
|
||||
mRemotePortChans[1] = remotePortBase+1;
|
||||
mRemotePortChans[0] = remotePortBase;
|
||||
mRemotePortChans[1] = remotePortBase+1;
|
||||
|
||||
mRtpFactor = mRtpClock;
|
||||
mRtpFactor = mRtpClock;
|
||||
|
||||
mBaseTimeReal = tvNow();
|
||||
mBaseTimeNtp = tvZero();
|
||||
mBaseTimeRtp = rtpTime;
|
||||
mBaseTimeReal = tvNow();
|
||||
mBaseTimeNtp = tvZero();
|
||||
mBaseTimeRtp = rtpTime;
|
||||
|
||||
mLastSrTimeReal = tvZero();
|
||||
mLastSrTimeNtp = tvZero();
|
||||
mLastSrTimeRtp = 0;
|
||||
|
||||
if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4)
|
||||
Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." );
|
||||
mLastSrTimeReal = tvZero();
|
||||
mLastSrTimeNtp = tvZero();
|
||||
mLastSrTimeRtp = 0;
|
||||
|
||||
if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4)
|
||||
Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." );
|
||||
}
|
||||
|
||||
void RtpSource::init( uint16_t seq )
|
||||
{
|
||||
Debug( 3, "Initialising sequence" );
|
||||
mBaseSeq = seq;
|
||||
mMaxSeq = seq;
|
||||
mBadSeq = RTP_SEQ_MOD + 1; // so seq == mBadSeq is false
|
||||
mCycles = 0;
|
||||
mReceivedPackets = 0;
|
||||
mReceivedPrior = 0;
|
||||
mExpectedPrior = 0;
|
||||
// other initialization
|
||||
mJitter = 0;
|
||||
mTransit = 0;
|
||||
Debug( 3, "Initialising sequence" );
|
||||
mBaseSeq = seq;
|
||||
mMaxSeq = seq;
|
||||
mBadSeq = RTP_SEQ_MOD + 1; // so seq == mBadSeq is false
|
||||
mCycles = 0;
|
||||
mReceivedPackets = 0;
|
||||
mReceivedPrior = 0;
|
||||
mExpectedPrior = 0;
|
||||
// other initialization
|
||||
mJitter = 0;
|
||||
mTransit = 0;
|
||||
}
|
||||
|
||||
bool RtpSource::updateSeq( uint16_t seq )
|
||||
{
|
||||
uint16_t uDelta = seq - mMaxSeq;
|
||||
uint16_t uDelta = seq - mMaxSeq;
|
||||
|
||||
// Source is not valid until MIN_SEQUENTIAL packets with
|
||||
// sequential sequence numbers have been received.
|
||||
Debug( 5, "Seq: %d", seq );
|
||||
// Source is not valid until MIN_SEQUENTIAL packets with
|
||||
// sequential sequence numbers have been received.
|
||||
Debug( 5, "Seq: %d", seq );
|
||||
|
||||
if ( mProbation)
|
||||
{
|
||||
// packet is in sequence
|
||||
if ( seq == mMaxSeq + 1)
|
||||
if ( mProbation)
|
||||
{
|
||||
Debug( 3, "Sequence in probation %d, in sequence", mProbation );
|
||||
mProbation--;
|
||||
mMaxSeq = seq;
|
||||
if ( mProbation == 0 )
|
||||
{
|
||||
init( seq );
|
||||
mReceivedPackets++;
|
||||
// packet is in sequence
|
||||
if ( seq == mMaxSeq + 1)
|
||||
{
|
||||
Debug( 3, "Sequence in probation %d, in sequence", mProbation );
|
||||
mProbation--;
|
||||
mMaxSeq = seq;
|
||||
if ( mProbation == 0 )
|
||||
{
|
||||
init( seq );
|
||||
mReceivedPackets++;
|
||||
return( true );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Sequence in probation %d, out of sequence", mProbation );
|
||||
mProbation = MIN_SEQUENTIAL - 1;
|
||||
mMaxSeq = seq;
|
||||
return( false );
|
||||
}
|
||||
return( true );
|
||||
}
|
||||
}
|
||||
else
|
||||
else if ( uDelta < MAX_DROPOUT )
|
||||
{
|
||||
Warning( "Sequence in probation %d, out of sequence", mProbation );
|
||||
mProbation = MIN_SEQUENTIAL - 1;
|
||||
mMaxSeq = seq;
|
||||
return( false );
|
||||
}
|
||||
return( true );
|
||||
}
|
||||
else if ( uDelta < MAX_DROPOUT )
|
||||
{
|
||||
if ( uDelta == 1 )
|
||||
{
|
||||
Debug( 3, "Packet in sequence, gap %d", uDelta );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Packet in sequence, gap %d", uDelta );
|
||||
}
|
||||
if ( uDelta == 1 )
|
||||
{
|
||||
Debug( 4, "Packet in sequence, gap %d", uDelta );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Packet in sequence, gap %d", uDelta );
|
||||
}
|
||||
|
||||
// in order, with permissible gap
|
||||
if ( seq < mMaxSeq )
|
||||
{
|
||||
// Sequence number wrapped - count another 64K cycle.
|
||||
mCycles += RTP_SEQ_MOD;
|
||||
// in order, with permissible gap
|
||||
if ( seq < mMaxSeq )
|
||||
{
|
||||
// Sequence number wrapped - count another 64K cycle.
|
||||
mCycles += RTP_SEQ_MOD;
|
||||
}
|
||||
mMaxSeq = seq;
|
||||
}
|
||||
mMaxSeq = seq;
|
||||
}
|
||||
else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER )
|
||||
{
|
||||
Warning( "Packet out of sequence, gap %d", uDelta );
|
||||
// the sequence number made a very large jump
|
||||
if ( seq == mBadSeq )
|
||||
else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER )
|
||||
{
|
||||
Debug( 3, "Restarting sequence" );
|
||||
// Two sequential packets -- assume that the other side
|
||||
// restarted without telling us so just re-sync
|
||||
// (i.e., pretend this was the first packet).
|
||||
init( seq );
|
||||
Warning( "Packet out of sequence, gap %d", uDelta );
|
||||
// the sequence number made a very large jump
|
||||
if ( seq == mBadSeq )
|
||||
{
|
||||
Debug( 3, "Restarting sequence" );
|
||||
// Two sequential packets -- assume that the other side
|
||||
// restarted without telling us so just re-sync
|
||||
// (i.e., pretend this was the first packet).
|
||||
init( seq );
|
||||
}
|
||||
else
|
||||
{
|
||||
mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1);
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1);
|
||||
return( false );
|
||||
Warning( "Packet duplicate or reordered, gap %d", uDelta );
|
||||
// duplicate or reordered packet
|
||||
return( false );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Packet duplicate or reordered, gap %d", uDelta );
|
||||
// duplicate or reordered packet
|
||||
return( false );
|
||||
}
|
||||
mReceivedPackets++;
|
||||
return( uDelta==1?true:false );
|
||||
mReceivedPackets++;
|
||||
return( uDelta==1?true:false );
|
||||
}
|
||||
|
||||
void RtpSource::updateJitter( const RtpDataHeader *header )
|
||||
{
|
||||
if ( mRtpFactor > 0 )
|
||||
{
|
||||
Debug( 5, "Delta rtp = %.6f", tvDiffSec( mBaseTimeReal ) );
|
||||
uint32_t localTimeRtp = mBaseTimeRtp + uint32_t( tvDiffSec( mBaseTimeReal ) * mRtpFactor );
|
||||
Debug( 5, "Local RTP time = %x", localTimeRtp );
|
||||
Debug( 5, "Packet RTP time = %x", ntohl(header->timestampN) );
|
||||
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
|
||||
Debug( 5, "Packet transit RTP time = %x", packetTransit );
|
||||
|
||||
if ( mTransit > 0 )
|
||||
if ( mRtpFactor > 0 )
|
||||
{
|
||||
// Jitter
|
||||
int d = packetTransit - mTransit;
|
||||
Debug( 5, "Jitter D = %d", d );
|
||||
if ( d < 0 )
|
||||
d = -d;
|
||||
//mJitter += (1./16.) * ((double)d - mJitter);
|
||||
mJitter += d - ((mJitter + 8) >> 4);
|
||||
Debug( 5, "Delta rtp = %.6f", tvDiffSec( mBaseTimeReal ) );
|
||||
uint32_t localTimeRtp = mBaseTimeRtp + uint32_t( tvDiffSec( mBaseTimeReal ) * mRtpFactor );
|
||||
Debug( 5, "Local RTP time = %x", localTimeRtp );
|
||||
Debug( 5, "Packet RTP time = %x", ntohl(header->timestampN) );
|
||||
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
|
||||
Debug( 5, "Packet transit RTP time = %x", packetTransit );
|
||||
|
||||
if ( mTransit > 0 )
|
||||
{
|
||||
// Jitter
|
||||
int d = packetTransit - mTransit;
|
||||
Debug( 5, "Jitter D = %d", d );
|
||||
if ( d < 0 )
|
||||
d = -d;
|
||||
//mJitter += (1./16.) * ((double)d - mJitter);
|
||||
mJitter += d - ((mJitter + 8) >> 4);
|
||||
}
|
||||
mTransit = packetTransit;
|
||||
}
|
||||
mTransit = packetTransit;
|
||||
}
|
||||
else
|
||||
{
|
||||
mJitter = 0;
|
||||
}
|
||||
Debug( 5, "RTP Jitter: %d", mJitter );
|
||||
else
|
||||
{
|
||||
mJitter = 0;
|
||||
}
|
||||
Debug( 5, "RTP Jitter: %d", mJitter );
|
||||
}
|
||||
|
||||
void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime )
|
||||
{
|
||||
struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) );
|
||||
struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) );
|
||||
|
||||
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
||||
|
||||
if ( mBaseTimeNtp.tv_sec == 0 )
|
||||
{
|
||||
mBaseTimeReal = tvNow();
|
||||
mBaseTimeNtp = ntpTime;
|
||||
mBaseTimeRtp = rtpTime;
|
||||
}
|
||||
else if ( !mRtpClock )
|
||||
{
|
||||
Debug( 5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x", mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime );
|
||||
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
||||
|
||||
if ( mBaseTimeNtp.tv_sec == 0 )
|
||||
{
|
||||
mBaseTimeReal = tvNow();
|
||||
mBaseTimeNtp = ntpTime;
|
||||
mBaseTimeRtp = rtpTime;
|
||||
}
|
||||
else if ( !mRtpClock )
|
||||
{
|
||||
Debug( 5, "lastSrNtpTime: %ld.%06ld, rtpTime: %x", mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime );
|
||||
Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime );
|
||||
|
||||
double diffNtpTime = tvDiffSec( mBaseTimeNtp, ntpTime );
|
||||
uint32_t diffRtpTime = rtpTime - mBaseTimeRtp;
|
||||
double diffNtpTime = tvDiffSec( mBaseTimeNtp, ntpTime );
|
||||
uint32_t diffRtpTime = rtpTime - mBaseTimeRtp;
|
||||
|
||||
//Debug( 5, "Real-diff: %.6f", diffRealTime );
|
||||
Debug( 5, "NTP-diff: %.6f", diffNtpTime );
|
||||
Debug( 5, "RTP-diff: %d", diffRtpTime );
|
||||
//Debug( 5, "Real-diff: %.6f", diffRealTime );
|
||||
Debug( 5, "NTP-diff: %.6f", diffNtpTime );
|
||||
Debug( 5, "RTP-diff: %d", diffRtpTime );
|
||||
|
||||
mRtpFactor = (uint32_t)(diffRtpTime / diffNtpTime);
|
||||
mRtpFactor = (uint32_t)(diffRtpTime / diffNtpTime);
|
||||
|
||||
Debug( 5, "RTPfactor: %d", mRtpFactor );
|
||||
}
|
||||
mLastSrTimeNtpSecs = ntpTimeSecs;
|
||||
mLastSrTimeNtpFrac = ntpTimeFrac;
|
||||
mLastSrTimeNtp = ntpTime;
|
||||
mLastSrTimeRtp = rtpTime;
|
||||
Debug( 5, "RTPfactor: %d", mRtpFactor );
|
||||
}
|
||||
mLastSrTimeNtpSecs = ntpTimeSecs;
|
||||
mLastSrTimeNtpFrac = ntpTimeFrac;
|
||||
mLastSrTimeNtp = ntpTime;
|
||||
mLastSrTimeRtp = rtpTime;
|
||||
}
|
||||
|
||||
void RtpSource::updateRtcpStats()
|
||||
{
|
||||
uint32_t extendedMax = mCycles + mMaxSeq;
|
||||
mExpectedPackets = extendedMax - mBaseSeq + 1;
|
||||
uint32_t extendedMax = mCycles + mMaxSeq;
|
||||
mExpectedPackets = extendedMax - mBaseSeq + 1;
|
||||
|
||||
Debug( 5, "Expected packets = %d", mExpectedPackets );
|
||||
Debug( 5, "Expected packets = %d", mExpectedPackets );
|
||||
|
||||
// The number of packets lost is defined to be the number of packets
|
||||
// expected less the number of packets actually received:
|
||||
mLostPackets = mExpectedPackets - mReceivedPackets;
|
||||
Debug( 5, "Lost packets = %d", mLostPackets );
|
||||
// The number of packets lost is defined to be the number of packets
|
||||
// expected less the number of packets actually received:
|
||||
mLostPackets = mExpectedPackets - mReceivedPackets;
|
||||
Debug( 5, "Lost packets = %d", mLostPackets );
|
||||
|
||||
uint32_t expectedInterval = mExpectedPackets - mExpectedPrior;
|
||||
Debug( 5, "Expected interval = %d", expectedInterval );
|
||||
mExpectedPrior = mExpectedPackets;
|
||||
uint32_t receivedInterval = mReceivedPackets - mReceivedPrior;
|
||||
Debug( 5, "Received interval = %d", receivedInterval );
|
||||
mReceivedPrior = mReceivedPackets;
|
||||
uint32_t lostInterval = expectedInterval - receivedInterval;
|
||||
Debug( 5, "Lost interval = %d", lostInterval );
|
||||
uint32_t expectedInterval = mExpectedPackets - mExpectedPrior;
|
||||
Debug( 5, "Expected interval = %d", expectedInterval );
|
||||
mExpectedPrior = mExpectedPackets;
|
||||
uint32_t receivedInterval = mReceivedPackets - mReceivedPrior;
|
||||
Debug( 5, "Received interval = %d", receivedInterval );
|
||||
mReceivedPrior = mReceivedPackets;
|
||||
uint32_t lostInterval = expectedInterval - receivedInterval;
|
||||
Debug( 5, "Lost interval = %d", lostInterval );
|
||||
|
||||
if ( expectedInterval == 0 || lostInterval <= 0 )
|
||||
mLostFraction = 0;
|
||||
else
|
||||
mLostFraction = (lostInterval << 8) / expectedInterval;
|
||||
Debug( 5, "Lost fraction = %d", mLostFraction );
|
||||
if ( expectedInterval == 0 || lostInterval <= 0 )
|
||||
mLostFraction = 0;
|
||||
else
|
||||
mLostFraction = (lostInterval << 8) / expectedInterval;
|
||||
Debug( 5, "Lost fraction = %d", mLostFraction );
|
||||
}
|
||||
|
||||
bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen )
|
||||
{
|
||||
const RtpDataHeader *rtpHeader;
|
||||
rtpHeader = (RtpDataHeader *)packet;
|
||||
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);
|
||||
bool thisM = rtpHeader->m || h264FragmentEnd;
|
||||
const RtpDataHeader *rtpHeader;
|
||||
rtpHeader = (RtpDataHeader *)packet;
|
||||
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) ) )
|
||||
{
|
||||
Hexdump( 4, packet+rtpHeaderSize, 16 );
|
||||
|
||||
if ( mFrameGood )
|
||||
if ( updateSeq( ntohs(rtpHeader->seqN) ) )
|
||||
{
|
||||
int extraHeader = 0;
|
||||
|
||||
if( mCodecId == AV_CODEC_ID_H264 )
|
||||
{
|
||||
int nalType = (packet[rtpHeaderSize] & 0x1f);
|
||||
|
||||
switch (nalType)
|
||||
Hexdump( 4, packet+rtpHeaderSize, 16 );
|
||||
|
||||
if ( mFrameGood )
|
||||
{
|
||||
case 24:
|
||||
{
|
||||
extraHeader = 2;
|
||||
break;
|
||||
}
|
||||
case 25: case 26: case 27:
|
||||
{
|
||||
extraHeader = 3;
|
||||
break;
|
||||
}
|
||||
// FU-A and FU-B
|
||||
case 28: case 29:
|
||||
{
|
||||
// Is this NAL the first NAL in fragmentation sequence
|
||||
if ( packet[rtpHeaderSize+1] & 0x80 )
|
||||
int extraHeader = 0;
|
||||
|
||||
if( mCodecId == AV_CODEC_ID_H264 )
|
||||
{
|
||||
// Now we will form new header of frame
|
||||
mFrame.append( "\x0\x0\x1\x0", 4 );
|
||||
// Reconstruct NAL header from FU headers
|
||||
*(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) |
|
||||
(packet[rtpHeaderSize] & 0xe0);
|
||||
int nalType = (packet[rtpHeaderSize] & 0x1f);
|
||||
Debug( 3, "Have H264 frame: nal type is %d", nalType );
|
||||
|
||||
switch (nalType)
|
||||
{
|
||||
case 24: // STAP-A
|
||||
{
|
||||
extraHeader = 2;
|
||||
break;
|
||||
}
|
||||
case 25: // STAP-B
|
||||
case 26: // MTAP-16
|
||||
case 27: // MTAP-24
|
||||
{
|
||||
extraHeader = 3;
|
||||
break;
|
||||
}
|
||||
// FU-A and FU-B
|
||||
case 28: case 29:
|
||||
{
|
||||
// Is this NAL the first NAL in fragmentation sequence
|
||||
if ( packet[rtpHeaderSize+1] & 0x80 )
|
||||
{
|
||||
// Now we will form new header of frame
|
||||
mFrame.append( "\x0\x0\x1\x0", 4 );
|
||||
// Reconstruct NAL header from FU headers
|
||||
*(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) |
|
||||
(packet[rtpHeaderSize] & 0xe0);
|
||||
}
|
||||
|
||||
extraHeader = 2;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Debug(3, "Unhandled nalType %d", nalType );
|
||||
}
|
||||
}
|
||||
|
||||
// Append NAL frame start code
|
||||
if ( !mFrame.size() )
|
||||
mFrame.append( "\x0\x0\x1", 3 );
|
||||
}
|
||||
|
||||
extraHeader = 2;
|
||||
break;
|
||||
}
|
||||
mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader );
|
||||
} else {
|
||||
Debug( 3, "NOT H264 frame: type is %d", mCodecId );
|
||||
}
|
||||
|
||||
// Append NAL frame start code
|
||||
if ( !mFrame.size() )
|
||||
mFrame.append( "\x0\x0\x1", 3 );
|
||||
}
|
||||
mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader );
|
||||
}
|
||||
|
||||
Hexdump( 4, mFrame.head(), 16 );
|
||||
Hexdump( 4, mFrame.head(), 16 );
|
||||
|
||||
if ( thisM )
|
||||
{
|
||||
if ( mFrameGood )
|
||||
{
|
||||
Debug( 2, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
|
||||
mFrameProcessed.setValueImmediate( false );
|
||||
mFrameReady.updateValueSignal( true );
|
||||
if ( !mFrameProcessed.getValueImmediate() )
|
||||
if ( thisM )
|
||||
{
|
||||
for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ )
|
||||
if( count > 1 )
|
||||
return( false );
|
||||
if ( mFrameGood )
|
||||
{
|
||||
Debug( 2, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
|
||||
mFrameProcessed.setValueImmediate( false );
|
||||
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 );
|
||||
}
|
||||
mFrameCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
}
|
||||
mFrame.clear();
|
||||
}
|
||||
mFrameCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
}
|
||||
mFrame.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( mFrame.size() )
|
||||
{
|
||||
Warning( "Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Discarding frame %d", mFrameCount );
|
||||
if ( mFrame.size() )
|
||||
{
|
||||
Warning( "Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size() );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Discarding frame %d", mFrameCount );
|
||||
}
|
||||
mFrameGood = false;
|
||||
mFrame.clear();
|
||||
}
|
||||
mFrameGood = false;
|
||||
mFrame.clear();
|
||||
}
|
||||
if ( thisM )
|
||||
{
|
||||
mFrameGood = true;
|
||||
prevM = true;
|
||||
}
|
||||
else
|
||||
prevM = false;
|
||||
if ( thisM )
|
||||
{
|
||||
mFrameGood = true;
|
||||
prevM = true;
|
||||
}
|
||||
else
|
||||
prevM = false;
|
||||
|
||||
updateJitter( rtpHeader );
|
||||
updateJitter( rtpHeader );
|
||||
|
||||
return( true );
|
||||
return( true );
|
||||
}
|
||||
|
||||
bool RtpSource::getFrame( Buffer &buffer )
|
||||
{
|
||||
Debug( 3, "Getting frame" );
|
||||
if ( !mFrameReady.getValueImmediate() )
|
||||
{
|
||||
// Allow for a couple of spurious returns
|
||||
for ( int count = 0; !mFrameReady.getUpdatedValue( 1 ); count++ )
|
||||
if ( count > 1 )
|
||||
return( false );
|
||||
}
|
||||
buffer = mFrame;
|
||||
mFrameReady.setValueImmediate( false );
|
||||
mFrameProcessed.updateValueSignal( true );
|
||||
Debug( 3, "Copied %d bytes", buffer.size() );
|
||||
return( true );
|
||||
Debug( 3, "Getting frame" );
|
||||
if ( !mFrameReady.getValueImmediate() )
|
||||
{
|
||||
// Allow for a couple of spurious returns
|
||||
for ( int count = 0; !mFrameReady.getUpdatedValue( 1 ); count++ )
|
||||
if ( count > 1 )
|
||||
return( false );
|
||||
}
|
||||
buffer = mFrame;
|
||||
mFrameReady.setValueImmediate( false );
|
||||
mFrameProcessed.updateValueSignal( true );
|
||||
Debug( 4, "Copied %d bytes", buffer.size() );
|
||||
return( true );
|
||||
}
|
||||
|
||||
#endif // HAVE_LIBAVCODEC
|
||||
|
|
|
@ -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
|
||||
|
1237
src/zm_zone.cpp
1237
src/zm_zone.cpp
File diff suppressed because it is too large
Load Diff
405
src/zmf.cpp
405
src/zmf.cpp
|
@ -42,9 +42,9 @@ them itself.
|
|||
|
||||
=head1 OPTIONS
|
||||
|
||||
-m, --monitor_id - ID of the monitor to use
|
||||
-h, --help - Display usage information
|
||||
-v, --version - Print the installed version of ZoneMinder
|
||||
-m, --monitor_id - ID of the monitor to use
|
||||
-h, --help - Display usage information
|
||||
-v, --version - Print the installed version of ZoneMinder
|
||||
|
||||
=cut
|
||||
|
||||
|
@ -73,237 +73,71 @@ them itself.
|
|||
|
||||
int OpenSocket( int monitor_id )
|
||||
{
|
||||
int sd = socket( AF_UNIX, SOCK_STREAM, 0);
|
||||
if ( sd < 0 )
|
||||
{
|
||||
Error( "Can't create socket: %s", strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
int sd = socket( AF_UNIX, SOCK_STREAM, 0);
|
||||
if ( sd < 0 )
|
||||
{
|
||||
Error( "Can't create socket: %s", strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
char sock_path[PATH_MAX] = "";
|
||||
snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id );
|
||||
if ( unlink( sock_path ) < 0 )
|
||||
{
|
||||
Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) );
|
||||
}
|
||||
char sock_path[PATH_MAX] = "";
|
||||
snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id );
|
||||
if ( unlink( sock_path ) < 0 )
|
||||
{
|
||||
Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) );
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
struct sockaddr_un addr;
|
||||
|
||||
strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) );
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) );
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 )
|
||||
{
|
||||
Error( "Can't bind: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 )
|
||||
{
|
||||
Error( "Can't bind: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
|
||||
if ( listen( sd, SOMAXCONN ) < 0 )
|
||||
{
|
||||
Error( "Can't listen: %s", strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
if ( listen( sd, SOMAXCONN ) < 0 )
|
||||
{
|
||||
Error( "Can't listen: %s", strerror(errno) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
struct sockaddr_un rem_addr;
|
||||
socklen_t rem_addr_len = sizeof(rem_addr);
|
||||
int new_sd = -1;
|
||||
if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 )
|
||||
{
|
||||
Error( "Can't accept: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
close( sd );
|
||||
struct sockaddr_un rem_addr;
|
||||
socklen_t rem_addr_len = sizeof(rem_addr);
|
||||
int new_sd = -1;
|
||||
if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 )
|
||||
{
|
||||
Error( "Can't accept: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
close( sd );
|
||||
|
||||
sd = new_sd;
|
||||
sd = new_sd;
|
||||
|
||||
Info( "Frame server socket open, awaiting images" );
|
||||
return( sd );
|
||||
Info( "Frame server socket open, awaiting images" );
|
||||
return( sd );
|
||||
}
|
||||
|
||||
int ReopenSocket( int &sd, int monitor_id )
|
||||
{
|
||||
close( sd );
|
||||
return( sd = OpenSocket( monitor_id ) );
|
||||
close( sd );
|
||||
return( sd = OpenSocket( monitor_id ) );
|
||||
}
|
||||
|
||||
void Usage()
|
||||
{
|
||||
fprintf( stderr, "zmf -m <monitor_id>\n" );
|
||||
fprintf( stderr, "Options:\n" );
|
||||
fprintf( stderr, " -m, --monitor <monitor_id> : Specify which monitor to use\n" );
|
||||
fprintf( stderr, " -h, --help : This screen\n" );
|
||||
fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" );
|
||||
exit( 0 );
|
||||
fprintf( stderr, "zmf -m <monitor_id>\n" );
|
||||
fprintf( stderr, "Options:\n" );
|
||||
fprintf( stderr, " -m, --monitor <monitor_id> : Specify which monitor to use\n" );
|
||||
fprintf( stderr, " -h, --help : This screen\n" );
|
||||
fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" );
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
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 );
|
||||
}
|
||||
Storage *Storage = monitor->getStorage();
|
||||
|
||||
char capt_path[PATH_MAX];
|
||||
char anal_path[PATH_MAX];
|
||||
snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", Storage->Path(), monitor->Id(), config.event_image_digits );
|
||||
snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", Storage->Path(), 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)
|
||||
{
|
||||
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 )
|
||||
=======
|
||||
self = argv[0];
|
||||
|
||||
srand( getpid() * time( 0 ) );
|
||||
|
@ -377,11 +211,12 @@ int main( int argc, char *argv[] )
|
|||
fprintf( stderr, "Can't find monitor with id of %d\n", id );
|
||||
exit( -1 );
|
||||
}
|
||||
Storage *Storage = monitor->getStorage();
|
||||
|
||||
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 );
|
||||
snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", Storage->Path(), monitor->Id(), config.event_image_digits );
|
||||
snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", Storage->Path(), monitor->Id(), config.event_image_digits );
|
||||
zmSetDefaultTermHandler();
|
||||
zmSetDefaultDieHandler();
|
||||
|
||||
|
@ -440,78 +275,78 @@ int main( int argc, char *argv[] )
|
|||
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) );
|
||||
// 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)
|
||||
{
|
||||
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 )
|
||||
{
|
||||
Error( "Can't read frame image data: %s", strerror(errno) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Socket closed at remote end" );
|
||||
}
|
||||
ReopenSocket( sd, monitor->Id() );
|
||||
continue;
|
||||
}
|
||||
// Print errors if there was a problem
|
||||
if ( n_bytes < 1 )
|
||||
{
|
||||
|
||||
static char subpath[PATH_MAX] = "";
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
struct tm *time = localtime( &frame_header.event_time );
|
||||
snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec );
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id );
|
||||
}
|
||||
Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length);
|
||||
if ( n_bytes < 0 )
|
||||
{
|
||||
Error( "Can't read frame image data: %s", strerror(errno) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Warning( "Socket closed at remote end" );
|
||||
}
|
||||
ReopenSocket( sd, monitor->Id() );
|
||||
continue;
|
||||
}
|
||||
|
||||
static char path[PATH_MAX] = "";
|
||||
snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id );
|
||||
Debug( 1, "Got image, writing to %s", path );
|
||||
static char subpath[PATH_MAX] = "";
|
||||
if ( config.use_deep_storage )
|
||||
{
|
||||
struct tm *time = localtime( &frame_header.event_time );
|
||||
snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec );
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id );
|
||||
}
|
||||
|
||||
FILE *fd = 0;
|
||||
if ( (fd = fopen( path, "w" )) < 0 )
|
||||
{
|
||||
Error( "Can't fopen '%s': %s", path, strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) )
|
||||
{
|
||||
Error( "Can't fwrite image data: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
fclose( fd );
|
||||
static char path[PATH_MAX] = "";
|
||||
snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id );
|
||||
Debug( 1, "Got image, writing to %s", path );
|
||||
|
||||
sigprocmask( SIG_UNBLOCK, &block_set, 0 );
|
||||
}
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
FILE *fd = 0;
|
||||
if ( (fd = fopen( path, "w" )) < 0 )
|
||||
{
|
||||
Error( "Can't fopen '%s': %s", path, strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) )
|
||||
{
|
||||
Error( "Can't fwrite image data: %s", strerror(errno) );
|
||||
exit( -1 );
|
||||
}
|
||||
fclose( fd );
|
||||
|
||||
sigprocmask( SIG_UNBLOCK, &block_set, 0 );
|
||||
}
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
}
|
||||
|
|
581
src/zms.cpp
581
src/zms.cpp
|
@ -28,318 +28,321 @@
|
|||
|
||||
bool ValidateAccess( User *user, int mon_id )
|
||||
{
|
||||
bool allowed = true;
|
||||
bool allowed = true;
|
||||
|
||||
if ( mon_id > 0 )
|
||||
{
|
||||
if ( user->getStream() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
if ( !user->canAccess( mon_id ) )
|
||||
allowed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( user->getEvents() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
}
|
||||
if ( !allowed )
|
||||
{
|
||||
Error( "Error, insufficient privileges for requested action" );
|
||||
exit( -1 );
|
||||
}
|
||||
return( allowed );
|
||||
if ( mon_id > 0 )
|
||||
{
|
||||
if ( user->getStream() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
if ( !user->canAccess( mon_id ) )
|
||||
allowed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( user->getEvents() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
}
|
||||
if ( !allowed )
|
||||
{
|
||||
Error( "Error, insufficient privileges for requested action" );
|
||||
exit( -1 );
|
||||
}
|
||||
return( allowed );
|
||||
}
|
||||
|
||||
int main( int argc, const char *argv[] )
|
||||
{
|
||||
self = argv[0];
|
||||
self = argv[0];
|
||||
|
||||
srand( getpid() * time( 0 ) );
|
||||
srand( getpid() * time( 0 ) );
|
||||
|
||||
enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR;
|
||||
enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG;
|
||||
char format[32] = "";
|
||||
int monitor_id = 0;
|
||||
time_t event_time = 0;
|
||||
int event_id = 0;
|
||||
unsigned int frame_id = 1;
|
||||
unsigned int scale = 100;
|
||||
unsigned int rate = 100;
|
||||
double maxfps = 10.0;
|
||||
unsigned int bitrate = 100000;
|
||||
unsigned int ttl = 0;
|
||||
EventStream::StreamMode replay = EventStream::MODE_SINGLE;
|
||||
char username[64] = "";
|
||||
char password[64] = "";
|
||||
char auth[64] = "";
|
||||
unsigned int connkey = 0;
|
||||
unsigned int playback_buffer = 0;
|
||||
enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR;
|
||||
enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG;
|
||||
char format[32] = "";
|
||||
int monitor_id = 0;
|
||||
time_t event_time = 0;
|
||||
int event_id = 0;
|
||||
unsigned int frame_id = 1;
|
||||
unsigned int scale = 100;
|
||||
unsigned int rate = 100;
|
||||
double maxfps = 10.0;
|
||||
unsigned int bitrate = 100000;
|
||||
unsigned int ttl = 0;
|
||||
EventStream::StreamMode replay = EventStream::MODE_SINGLE;
|
||||
char username[64] = "";
|
||||
char password[64] = "";
|
||||
char auth[64] = "";
|
||||
unsigned int connkey = 0;
|
||||
unsigned int playback_buffer = 0;
|
||||
|
||||
bool nph = false;
|
||||
const char *basename = strrchr( argv[0], '/' );
|
||||
if (basename) //if we found a / lets skip past it
|
||||
basename++;
|
||||
else //argv[0] will not always contain the full path, but rather just the script name
|
||||
basename = argv[0];
|
||||
const char *nph_prefix = "nph-";
|
||||
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) )
|
||||
{
|
||||
nph = true;
|
||||
}
|
||||
|
||||
zmLoadConfig();
|
||||
bool nph = false;
|
||||
const char *basename = strrchr( argv[0], '/' );
|
||||
if (basename) //if we found a / lets skip past it
|
||||
basename++;
|
||||
else //argv[0] will not always contain the full path, but rather just the script name
|
||||
basename = argv[0];
|
||||
const char *nph_prefix = "nph-";
|
||||
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) )
|
||||
{
|
||||
nph = true;
|
||||
}
|
||||
|
||||
zmLoadConfig();
|
||||
|
||||
logInit( "zms" );
|
||||
|
||||
ssedetect();
|
||||
logInit( "zms" );
|
||||
|
||||
ssedetect();
|
||||
|
||||
zmSetDefaultTermHandler();
|
||||
zmSetDefaultDieHandler();
|
||||
zmSetDefaultTermHandler();
|
||||
zmSetDefaultDieHandler();
|
||||
|
||||
const char *query = getenv( "QUERY_STRING" );
|
||||
if ( query )
|
||||
{
|
||||
Debug( 1, "Query: %s", query );
|
||||
|
||||
char temp_query[1024];
|
||||
strncpy( temp_query, query, sizeof(temp_query) );
|
||||
char *q_ptr = temp_query;
|
||||
char *parms[16]; // Shouldn't be more than this
|
||||
int parm_no = 0;
|
||||
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) )
|
||||
{
|
||||
parm_no++;
|
||||
q_ptr = NULL;
|
||||
}
|
||||
|
||||
for ( int p = 0; p < parm_no; p++ )
|
||||
{
|
||||
char *name = strtok( parms[p], "=" );
|
||||
char *value = strtok( NULL, "=" );
|
||||
if ( !value )
|
||||
value = (char *)"";
|
||||
if ( !strcmp( name, "source" ) )
|
||||
{
|
||||
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
|
||||
}
|
||||
else if ( !strcmp( name, "mode" ) )
|
||||
{
|
||||
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
|
||||
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
|
||||
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
|
||||
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
|
||||
}
|
||||
else if ( !strcmp( name, "format" ) )
|
||||
strncpy( format, value, sizeof(format) );
|
||||
else if ( !strcmp( name, "monitor" ) )
|
||||
monitor_id = atoi( value );
|
||||
else if ( !strcmp( name, "time" ) )
|
||||
event_time = atoi( value );
|
||||
else if ( !strcmp( name, "event" ) )
|
||||
event_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "frame" ) )
|
||||
frame_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "scale" ) )
|
||||
scale = atoi( value );
|
||||
else if ( !strcmp( name, "rate" ) )
|
||||
rate = atoi( value );
|
||||
else if ( !strcmp( name, "maxfps" ) )
|
||||
maxfps = atof( value );
|
||||
else if ( !strcmp( name, "bitrate" ) )
|
||||
bitrate = atoi( value );
|
||||
else if ( !strcmp( name, "ttl" ) )
|
||||
ttl = atoi(value);
|
||||
else if ( !strcmp( name, "replay" ) )
|
||||
{
|
||||
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
|
||||
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
|
||||
}
|
||||
else if ( !strcmp( name, "connkey" ) )
|
||||
connkey = atoi(value);
|
||||
else if ( !strcmp( name, "buffer" ) )
|
||||
playback_buffer = atoi(value);
|
||||
else if ( config.opt_use_auth )
|
||||
{
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
const char *query = getenv( "QUERY_STRING" );
|
||||
if ( query )
|
||||
{
|
||||
Debug( 1, "Query: %s", query );
|
||||
|
||||
char temp_query[1024];
|
||||
strncpy( temp_query, query, sizeof(temp_query) );
|
||||
char *q_ptr = temp_query;
|
||||
char *parms[16]; // Shouldn't be more than this
|
||||
int parm_no = 0;
|
||||
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) )
|
||||
{
|
||||
parm_no++;
|
||||
q_ptr = NULL;
|
||||
}
|
||||
|
||||
for ( int p = 0; p < parm_no; p++ )
|
||||
{
|
||||
char *name = strtok( parms[p], "=" );
|
||||
char *value = strtok( NULL, "=" );
|
||||
if ( !value )
|
||||
value = (char *)"";
|
||||
if ( !strcmp( name, "source" ) )
|
||||
{
|
||||
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
|
||||
}
|
||||
else if ( !strcmp( name, "mode" ) )
|
||||
{
|
||||
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
|
||||
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
|
||||
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
|
||||
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
|
||||
}
|
||||
else if ( !strcmp( name, "format" ) )
|
||||
strncpy( format, value, sizeof(format) );
|
||||
else if ( !strcmp( name, "monitor" ) )
|
||||
monitor_id = atoi( value );
|
||||
else if ( !strcmp( name, "time" ) )
|
||||
event_time = atoi( value );
|
||||
else if ( !strcmp( name, "event" ) )
|
||||
event_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "frame" ) )
|
||||
frame_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "scale" ) )
|
||||
scale = atoi( value );
|
||||
else if ( !strcmp( name, "rate" ) )
|
||||
rate = atoi( value );
|
||||
else if ( !strcmp( name, "maxfps" ) )
|
||||
maxfps = atof( value );
|
||||
else if ( !strcmp( name, "bitrate" ) )
|
||||
bitrate = atoi( value );
|
||||
else if ( !strcmp( name, "ttl" ) )
|
||||
ttl = atoi(value);
|
||||
else if ( !strcmp( name, "replay" ) )
|
||||
{
|
||||
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
|
||||
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
|
||||
}
|
||||
else if ( !strcmp( name, "connkey" ) )
|
||||
connkey = atoi(value);
|
||||
else if ( !strcmp( name, "buffer" ) )
|
||||
playback_buffer = atoi(value);
|
||||
else if ( config.opt_use_auth )
|
||||
{
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
strncpy( username, value, sizeof(username) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "auth" ) )
|
||||
{
|
||||
strncpy( auth, value, sizeof(auth) );
|
||||
}
|
||||
}
|
||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
strncpy( username, value, sizeof(username) );
|
||||
}
|
||||
if ( !strcmp( name, "pass" ) )
|
||||
{
|
||||
strncpy( password, value, sizeof(password) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( config.opt_use_auth )
|
||||
{
|
||||
User *user = 0;
|
||||
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
{
|
||||
if ( *username )
|
||||
{
|
||||
user = zmLoadUser( username );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( *auth )
|
||||
{
|
||||
user = zmLoadAuthUser( auth, config.auth_hash_ips );
|
||||
}
|
||||
}
|
||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||
{
|
||||
if ( *username && *password )
|
||||
{
|
||||
user = zmLoadUser( username, password );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !user )
|
||||
{
|
||||
Error( "Unable to authenticate user" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
}
|
||||
ValidateAccess( user, monitor_id );
|
||||
}
|
||||
|
||||
setbuf( stdout, 0 );
|
||||
if ( nph )
|
||||
{
|
||||
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
|
||||
}
|
||||
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
|
||||
|
||||
time_t now = time( 0 );
|
||||
char date_string[64];
|
||||
strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) );
|
||||
|
||||
fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" );
|
||||
fprintf( stdout, "Last-Modified: %s\r\n", date_string );
|
||||
fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" );
|
||||
fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" );
|
||||
fprintf( stdout, "Pragma: no-cache\r\n");
|
||||
// Removed as causing more problems than it fixed.
|
||||
//if ( !nph )
|
||||
//{
|
||||
//fprintf( stdout, "Content-Length: 0\r\n");
|
||||
//}
|
||||
|
||||
if ( source == ZMS_MONITOR )
|
||||
{
|
||||
MonitorStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
stream.setStreamMaxFPS( maxfps );
|
||||
stream.setStreamTTL( ttl );
|
||||
stream.setStreamQueue( connkey );
|
||||
stream.setStreamBuffer( playback_buffer );
|
||||
if ( ! stream.setStreamStart( monitor_id ) ) {
|
||||
Error( "Unable to connect to zmc process for monitor %d", monitor_id );
|
||||
fprintf( stderr, "Unable to connect to zmc process. Please ensure that it is running." );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
strncpy( username, value, sizeof(username) );
|
||||
}
|
||||
stream.setStreamType( MonitorStream::STREAM_JPEG );
|
||||
}
|
||||
else if ( mode == ZMS_RAW )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_RAW );
|
||||
}
|
||||
else if ( mode == ZMS_ZIP )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_ZIP );
|
||||
}
|
||||
else if ( mode == ZMS_SINGLE )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_SINGLE );
|
||||
}
|
||||
else
|
||||
{
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "auth" ) )
|
||||
{
|
||||
strncpy( auth, value, sizeof(auth) );
|
||||
}
|
||||
}
|
||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
strncpy( username, value, sizeof(username) );
|
||||
}
|
||||
if ( !strcmp( name, "pass" ) )
|
||||
{
|
||||
strncpy( password, value, sizeof(password) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( config.opt_use_auth )
|
||||
{
|
||||
User *user = 0;
|
||||
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
{
|
||||
if ( *username )
|
||||
{
|
||||
user = zmLoadUser( username );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( *auth )
|
||||
{
|
||||
user = zmLoadAuthUser( auth, config.auth_hash_ips );
|
||||
}
|
||||
}
|
||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||
{
|
||||
if ( *username && *password )
|
||||
{
|
||||
user = zmLoadUser( username, password );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !user )
|
||||
{
|
||||
Error( "Unable to authenticate user" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
}
|
||||
ValidateAccess( user, monitor_id );
|
||||
}
|
||||
|
||||
setbuf( stdout, 0 );
|
||||
if ( nph )
|
||||
{
|
||||
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
|
||||
}
|
||||
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
|
||||
|
||||
time_t now = time( 0 );
|
||||
char date_string[64];
|
||||
strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) );
|
||||
|
||||
fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" );
|
||||
fprintf( stdout, "Last-Modified: %s\r\n", date_string );
|
||||
fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" );
|
||||
fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" );
|
||||
fprintf( stdout, "Pragma: no-cache\r\n");
|
||||
// Removed as causing more problems than it fixed.
|
||||
//if ( !nph )
|
||||
//{
|
||||
//fprintf( stdout, "Content-Length: 0\r\n");
|
||||
//}
|
||||
|
||||
if ( source == ZMS_MONITOR )
|
||||
{
|
||||
MonitorStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
stream.setStreamMaxFPS( maxfps );
|
||||
stream.setStreamTTL( ttl );
|
||||
stream.setStreamQueue( connkey );
|
||||
stream.setStreamBuffer( playback_buffer );
|
||||
if ( ! stream.setStreamStart( monitor_id ) ) {
|
||||
Error( "Unable to connect to zmc process for monitor %d", monitor_id );
|
||||
fprintf( stderr, "Unable to connect to zmc process. Please ensure that it is running." );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_JPEG );
|
||||
}
|
||||
else if ( mode == ZMS_RAW )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_RAW );
|
||||
}
|
||||
else if ( mode == ZMS_ZIP )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_ZIP );
|
||||
}
|
||||
else if ( mode == ZMS_SINGLE )
|
||||
{
|
||||
stream.setStreamType( MonitorStream::STREAM_SINGLE );
|
||||
}
|
||||
else
|
||||
{
|
||||
#if HAVE_LIBAVCODEC
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
stream.setStreamType( MonitorStream::STREAM_MPEG );
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
stream.setStreamType( MonitorStream::STREAM_MPEG );
|
||||
#else // HAVE_LIBAVCODEC
|
||||
Error( "MPEG streaming of '%s' attempted while disabled", query );
|
||||
fprintf( stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
Error( "MPEG streaming of '%s' attempted while disabled", query );
|
||||
fprintf( stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
#endif // HAVE_LIBAVCODEC
|
||||
}
|
||||
stream.runStream();
|
||||
}
|
||||
else if ( source == ZMS_EVENT )
|
||||
{
|
||||
EventStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
stream.setStreamMaxFPS( maxfps );
|
||||
stream.setStreamMode( replay );
|
||||
stream.setStreamQueue( connkey );
|
||||
if ( monitor_id && event_time )
|
||||
{
|
||||
stream.setStreamStart( monitor_id, event_time );
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.setStreamStart( event_id, frame_id );
|
||||
}
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
stream.setStreamType( EventStream::STREAM_JPEG );
|
||||
}
|
||||
else
|
||||
}
|
||||
stream.runStream();
|
||||
}
|
||||
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 );
|
||||
stream.setStreamMaxFPS( maxfps );
|
||||
stream.setStreamMode( replay );
|
||||
stream.setStreamQueue( connkey );
|
||||
if ( monitor_id && event_time )
|
||||
{
|
||||
stream.setStreamStart( monitor_id, event_time );
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.setStreamStart( event_id, frame_id );
|
||||
}
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
stream.setStreamType( EventStream::STREAM_JPEG );
|
||||
}
|
||||
else
|
||||
{
|
||||
#if HAVE_LIBAVCODEC
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
stream.setStreamType( EventStream::STREAM_MPEG );
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
stream.setStreamType( EventStream::STREAM_MPEG );
|
||||
#else // HAVE_LIBAVCODEC
|
||||
Error( "MPEG streaming of '%s' attempted while disabled", query );
|
||||
fprintf( stderr, "MPEG streaming is disabled.\nYou should ensure the ffmpeg libraries are installed and detected and rebuild to use this functionality.\n" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
Error( "MPEG streaming of '%s' attempted while disabled", query );
|
||||
fprintf( stderr, "MPEG streaming is disabled.\nYou should ensure the ffmpeg libraries are installed and detected and rebuild to use this functionality.\n" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
return( -1 );
|
||||
#endif // HAVE_LIBAVCODEC
|
||||
}
|
||||
stream.runStream();
|
||||
}
|
||||
stream.runStream();
|
||||
}
|
||||
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
|
||||
return( 0 );
|
||||
return( 0 );
|
||||
}
|
||||
|
|
1449
src/zmu.cpp
1449
src/zmu.cpp
File diff suppressed because it is too large
Load Diff
|
@ -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',
|
||||
);
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package app.Console
|
||||
:: @since CakePHP(tm) v 2.0
|
||||
:: @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
:: In order for this script to work as intended, the cake\console\ folder must be in your PATH
|
||||
|
||||
@echo.
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package app.Console
|
||||
:: @since CakePHP(tm) v 2.0
|
||||
:: @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
:: In order for this script to work as intended, the cake\console\ folder must be in your PATH
|
||||
|
||||
@echo.
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
|
|
|
@ -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();
|
|
@ -1,30 +1,30 @@
|
|||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package app.Console
|
||||
:: @since CakePHP(tm) v 2.0
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
:: In order for this script to work as intended, the cake\console\ folder must be in your PATH
|
||||
|
||||
@echo.
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package app.Console
|
||||
:: @since CakePHP(tm) v 2.0
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
:: In order for this script to work as intended, the cake\console\ folder must be in your PATH
|
||||
|
||||
@echo.
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package Cake.Console
|
||||
:: @since CakePHP(tm) v 1.2.0.5012
|
||||
:: @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
::
|
||||
:: Bake is a shell script for running CakePHP bake script
|
||||
::
|
||||
:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
::
|
||||
:: Licensed under The MIT License
|
||||
:: Redistributions of files must retain the above copyright notice.
|
||||
::
|
||||
:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
:: @link http://cakephp.org CakePHP(tm) Project
|
||||
:: @package Cake.Console
|
||||
:: @since CakePHP(tm) v 1.2.0.5012
|
||||
:: @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
::
|
||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
|
||||
@echo off
|
||||
|
||||
SET app=%0
|
||||
SET lib=%~dp0
|
||||
|
||||
php -q "%lib%cake.php" -working "%CD% " %*
|
||||
|
||||
echo.
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
|
|
|
@ -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
|
@ -3,70 +3,150 @@ require_once( 'database.php' );
|
|||
require_once( 'Storage.php' );
|
||||
|
||||
class Event {
|
||||
public function __construct( $IdOrRow ) {
|
||||
$row = NULL;
|
||||
if ( $IdOrRow ) {
|
||||
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
|
||||
$row = dbFetchOne( 'SELECT *,unix_timestamp(StartTime) as Time FROM Events WHERE Id=?', NULL, array( $IdOrRow ) );
|
||||
if ( ! $row ) {
|
||||
Error("Unable to load Event record for Id=" . $IdOrRow );
|
||||
}
|
||||
} elseif ( is_array( $IdOrRow ) ) {
|
||||
$row = $IdOrRow;
|
||||
} else {
|
||||
Error("Unknown argument passed to Event Constructor ($IdOrRow)");
|
||||
return;
|
||||
}
|
||||
} # end if isset($IdOrRow)
|
||||
|
||||
if ( $row ) {
|
||||
foreach ($row as $k => $v) {
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
} else {
|
||||
Error("No row for Event " . $IdOrRow );
|
||||
}
|
||||
} // end function __construct
|
||||
public function Storage() {
|
||||
return new Storage( $this->{'StorageId'} );
|
||||
}
|
||||
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 __construct( $IdOrRow ) {
|
||||
$row = NULL;
|
||||
if ( $IdOrRow ) {
|
||||
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
|
||||
$row = dbFetchOne( 'SELECT *,unix_timestamp(StartTime) as Time FROM Events WHERE Id=?', NULL, array( $IdOrRow ) );
|
||||
if ( ! $row ) {
|
||||
Error("Unable to load Event record for Id=" . $IdOrRow );
|
||||
}
|
||||
} elseif ( is_array( $IdOrRow ) ) {
|
||||
$row = $IdOrRow;
|
||||
} else {
|
||||
Error("Unknown argument passed to Event Constructor ($IdOrRow)");
|
||||
return;
|
||||
}
|
||||
} # end if isset($IdOrRow)
|
||||
|
||||
if ( $row ) {
|
||||
foreach ($row as $k => $v) {
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
} else {
|
||||
Error("No row for Event " . $IdOrRow );
|
||||
}
|
||||
} // end function __construct
|
||||
public function Storage() {
|
||||
return new Storage( $this->{'StorageId'} );
|
||||
}
|
||||
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 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();
|
||||
}
|
||||
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'}
|
||||
;
|
||||
}
|
||||
|
||||
public function Path() {
|
||||
$Storage = $this->Storage();
|
||||
return $Storage->Path().'/'.$this->Relative_Path();
|
||||
}
|
||||
public function Relative_Path() {
|
||||
$event_path = "";
|
||||
return( $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 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,8 +28,15 @@ 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;
|
||||
}
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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' );
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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' );
|
||||
?>
|
||||
|
@ -599,9 +598,9 @@ function exportEventImagesMaster( $eids )
|
|||
foreach ($eids as $eid) {
|
||||
//get monitor id and event id
|
||||
$sql = 'SELECT E.MonitorId, E.StartTime, E.Id
|
||||
FROM Monitors AS M INNER JOIN Events AS E ON (M.Id = E.MonitorId)
|
||||
WHERE E.Id = ?
|
||||
';
|
||||
FROM Monitors AS M INNER JOIN Events AS E ON (M.Id = E.MonitorId)
|
||||
WHERE E.Id = ?
|
||||
';
|
||||
$event = dbFetchOne( $sql, NULL, array( $eid ) );
|
||||
$eventMonitorId[$eid] = $event['MonitorId'];
|
||||
$eventPath[$eid] = mygetEventPath( $event );
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
?>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue