Merge branch 'master' of github.com:ZoneMinder/zoneminder

This commit is contained in:
Isaac Connor 2021-10-12 12:25:01 -04:00
commit 0ba3d863a6
147 changed files with 3812 additions and 3369 deletions

View File

@ -1,17 +1,19 @@
task: task:
name: freebsd-build name: freebsd-build
freebsd_instance: freebsd_instance:
image_family: freebsd-12-2 matrix:
- image_family: freebsd-12-2
- image_family: freebsd-13-0
prepare_script: prepare_script:
- pkg install -yq git cmake pkgconf jpeg-turbo mysql80-client ffmpeg libvncserver libjwt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap - pkg install -yq git cmake pkgconf jpeg-turbo mysql80-client ffmpeg libvncserver libjwt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap v4l_compat
configure_script: configure_script:
- git submodule update --init --recursive - git submodule update --init --recursive
- mkdir build - mkdir build
- cd build - cd build
- cmake --version - cmake --version
- cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DCMAKE_C_FLAGS="-Wno-deprecated-declarations" -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations"
build_script: build_script:
- cd build - cd build

2
.github/FUNDING.yml vendored
View File

@ -2,7 +2,7 @@
github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: zoneminder # Replace with a single Patreon username patreon: zoneminder # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: zoneminder # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry

View File

@ -14,6 +14,8 @@ jobs:
os_dist: os_dist:
- os: debian - os: debian
dist: buster dist: buster
- os: debian
dist: bullseye
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

1
.gitignore vendored
View File

@ -124,6 +124,7 @@ src/zmc
src/zmf src/zmf
src/zms src/zms
src/zmu src/zmu
src/zm_rtsp_server
src/zoneminder-zmc.8 src/zoneminder-zmc.8
src/zoneminder-zmc.8.gz src/zoneminder-zmc.8.gz
src/zoneminder-zmf.8 src/zoneminder-zmf.8

View File

@ -10,7 +10,8 @@ deb:
tags: tags:
- docker - docker
script: script:
- yes "" | ./utils/do_debian_package.sh --snapshot=stable --branch=1.36.1 --type=binary --interactive=no --dput=no --debbuild-extra=--no-sign || true - yes "" | ./utils/do_debian_package.sh --snapshot=stable --type=binary --interactive=no --dput=no --debbuild-extra=--no-sign || true
timeout: 2h
artifacts: artifacts:
paths: paths:
- '*.deb' - '*.deb'

View File

@ -187,6 +187,8 @@ set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH
"Relative path used to install ZoneMinder's Man pages into a "Relative path used to install ZoneMinder's Man pages into a
non-standard folder. Most Linux users will not need to change this. non-standard folder. Most Linux users will not need to change this.
BSD users may need to set this.") BSD users may need to set this.")
set(ZM_CAKEPHP_CACHE "Apc" CACHE STRING
"Set the CakePHP cache engine, default: Apc")
# Supported crypto backends. Using OpenSSL by default to be compatible with jwt-cpp. # Supported crypto backends. Using OpenSSL by default to be compatible with jwt-cpp.
set(ZM_CRYPTO_BACKEND_OPTIONS gnutls openssl) set(ZM_CRYPTO_BACKEND_OPTIONS gnutls openssl)
@ -207,6 +209,10 @@ if(NOT ZM_JWT_BACKEND IN_LIST ZM_JWT_BACKEND_OPTIONS)
endif() endif()
# Reassign some variables if a target distro has been specified # Reassign some variables if a target distro has been specified
if(ZM_TARGET_DISTRO MATCHES "^fc")
set(ZM_CAKEPHP_CACHE "Memcached")
endif()
if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc"))
set(ZM_RUNDIR "/var/run/zoneminder") set(ZM_RUNDIR "/var/run/zoneminder")
set(ZM_SOCKDIR "/var/lib/zoneminder/sock") set(ZM_SOCKDIR "/var/lib/zoneminder/sock")
@ -262,7 +268,6 @@ if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/sy
endif() endif()
# System checks # System checks
check_include_file("linux/videodev2.h" HAVE_LINUX_VIDEODEV2_H)
check_include_file("execinfo.h" HAVE_EXECINFO_H) check_include_file("execinfo.h" HAVE_EXECINFO_H)
if(HAVE_EXECINFO_H) if(HAVE_EXECINFO_H)
check_function_exists("backtrace" HAVE_DECL_BACKTRACE) check_function_exists("backtrace" HAVE_DECL_BACKTRACE)
@ -522,17 +527,14 @@ if((NOT HAVE_LIBJWT) AND (NOT HAVE_LIBOPENSSL))
message(FATAL_ERROR "Using the jwt-cpp backend requires OpenSSL as crypto backend.") message(FATAL_ERROR "Using the jwt-cpp backend requires OpenSSL as crypto backend.")
endif() endif()
# Check for V4L header files and enable ZM_HAS_V4L, ZM_HAS_V4L2 accordingly find_package(V4L2)
# Setting to zeros first is required because ZM uses #define for these if(TARGET V4L2::videodev2)
set(ZM_HAS_V4L 0)
set(ZM_HAS_V4L2 0)
if(HAVE_LINUX_VIDEODEV2_H)
set(ZM_HAS_V4L 1)
set(ZM_HAS_V4L2 1) set(ZM_HAS_V4L2 1)
endif() else()
if(NOT HAVE_LINUX_VIDEODEV2_H) set(ZM_HAS_V4L2 0)
message(AUTHOR_WARNING "Video 4 Linux headers weren't found - Analog and USB camera support will not be available") message(AUTHOR_WARNING "Video 4 Linux headers weren't found - Analog and USB camera support will not be available")
endif() endif()
# Check for PCRE and enable ZM_PCRE accordingly # Check for PCRE and enable ZM_PCRE accordingly
set(ZM_PCRE 0) set(ZM_PCRE 0)
if(HAVE_LIBPCRE AND HAVE_PCRE_H) if(HAVE_LIBPCRE AND HAVE_PCRE_H)

View File

@ -30,7 +30,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
- RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org)
- Fedora via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org)
- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) - OpenSuse via [third party repository](https://wiki.zoneminder.com/Installing_using_ZoneMinder_RPMs_for_SuSE)
- Mageia from their default repository - Mageia from their default repository
- Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) - Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/)
- Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder) - Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder)

View File

@ -0,0 +1,99 @@
#[=======================================================================[.rst:
FindV4L2
----------
Find V4L2 headers and libv4l2
This module accepts optional COMPONENTS:
videodev2 (default)
libv4l2
IMPORTED Targets
^^^^^^^^^^^^^^^^
This module defines the following :prop_tgt:`IMPORTED` targets::
``V4L2::videodev2``
The Video for Linux Two header file, if found.
``V4L2::libv4l2``
A thin abstraction layer on top of video4linux2 devices, if found.
Result Variables
^^^^^^^^^^^^^^^^
``V4L2_FOUND``
System has v4l2 support. If no components are specified only the videodev2.h header has to be found.
``V4L2_INCLUDE_DIRS``
The v4l2 include directories.
``V4L2_LIBRARIES``
The libraries needed to have v4l2 support according to the specified components.
#]=======================================================================]
find_path(V4L2_VIDEODEV2_INCLUDE_DIR
NAMES linux/videodev2.h)
mark_as_advanced(V4L2_VIDEODEV2_INCLUDE_DIR)
if(EXISTS "${V4L2_VIDEODEV2_INCLUDE_DIR}")
set(V4L2_videodev2_FOUND TRUE)
else()
set(V4L2_videodev2_FOUND FALSE)
endif()
pkg_check_modules(PC_V4L2_LIBV4L2 QUIET libv4l2)
find_path(V4L2_LIBV4L2_INCLUDE_DIR
NAMES libv4l2.h
HINTS
${PC_V4L2_LIBV4L2_INCLUDEDIR}
${PC_V4L2_LIBV4L2_INCLUDE_DIRS})
mark_as_advanced(V4L2_LIBV4L2_INCLUDE_DIR)
find_library(V4L2_LIBV4L2_LIBRARY
NAMES ${PC_V4L2_LIBV4L2_LIBRARIES}
HINTS
${PC_V4L2_LIBV4L2_LIBDIR}
${PC_V4L2_LIBV4L2_LIBRARY_DIR})
mark_as_advanced(V4L2_LIBV4L2_LIBRARY)
if(EXISTS "${V4L2_LIBV4L2_INCLUDE_DIR}" AND
EXISTS "${V4L2_LIBV4L2_LIBRARY}")
set(V4L2_libv4l2_FOUND TRUE)
else()
set(V4L2_libv4l2_FOUND FALSE)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(V4L2
REQUIRED_VARS
V4L2_VIDEODEV2_INCLUDE_DIR
HANDLE_COMPONENTS)
set(V4L2_INCLUDE_DIRS)
set(V4L2_LIBRARIES)
if(V4L2_videodev2_FOUND)
set(V4L2_VIDEODEV2_INCLUDE_DIRS ${V4L2_VIDEODEV2_INCLUDE_DIR})
list(APPEND V4L2_INCLUDE2_DIRS
"${V4L2_VIDEODEV2_INCLUDE_DIRS}")
add_library(V4L2::videodev2 INTERFACE IMPORTED)
set_target_properties(V4L2::videodev2 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${V4L2_VIDEODEV2_INCLUDE_DIRS}")
endif()
if(V4L2_libv4l2_FOUND)
set(V4L2_LIBV4L2_INCLUDE_DIRS ${V4L2_LIBV4L2_INCLUDE_DIR})
set(V4L2_LIBV4L2_LIBRARIES ${V4L2_LIBV4L2_LIBRARY})
list(APPEND V4L2_INCLUDE_DIRS
"${V4L2_LIBV4L2_INCLUDE_DIRS}")
list(APPEND V4L2_LIBRARIES
"${V4L2_LIBV4L2_LIBRARIES}")
add_library(V4L2::libv4l2 UNKNOWN IMPORTED)
set_target_properties(V4L2::libv4l2 PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${V4L2_LIBV4L2_INCLUDE_DIRS}"
IMPORTED_LOCATION "${V4L2_LIBV4L2_LIBRARY}")
endif()

View File

@ -536,7 +536,7 @@ CREATE TABLE `Monitors` (
`ZoneCount` TINYINT NOT NULL DEFAULT 0, `ZoneCount` TINYINT NOT NULL DEFAULT 0,
`Refresh` int(10) unsigned default NULL, `Refresh` int(10) unsigned default NULL,
`Latitude` DECIMAL(10,8), `Latitude` DECIMAL(10,8),
`Longitude` DECIMAL(10,8), `Longitude` DECIMAL(11,8),
`RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE,
`RTSPStreamName` varchar(255) NOT NULL default '', `RTSPStreamName` varchar(255) NOT NULL default '',
`Importance` enum('Not','Less','Normal'), `Importance` enum('Not','Less','Normal'),
@ -1080,11 +1080,11 @@ CREATE TABLE MontageLayouts (
PRIMARY KEY (`Id`) PRIMARY KEY (`Id`)
); );
INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' );
INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"50%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' );
INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{"float":"left", "width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{"float":"left", "width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' );
INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"24.5%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' );
INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"19%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' );
CREATE TABLE Sessions ( CREATE TABLE Sessions (
id char(32) not null, id char(32) not null,

1
db/zm_update-1.36.6.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8);

1
db/zm_update-1.37.1.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8);

4
db/zm_update-1.37.2.sql Normal file
View File

@ -0,0 +1,4 @@
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' WHERE `Name`='Freeform';
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='2 Wide';
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='4 Wide';
UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='5 Wide';

View File

@ -17,21 +17,26 @@
# This will tell zoneminder's cmake process we are building against a known distro # This will tell zoneminder's cmake process we are building against a known distro
%global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}}
# Fedora needs apcu backwards compatibility module
%if 0%{?fedora}
%global with_apcu_bc 1
%endif
# Newer php's keep json functions in a subpackage # Newer php's keep json functions in a subpackage
%if 0%{?fedora} || 0%{?rhel} >= 8 %if 0%{?fedora} || 0%{?rhel} >= 8
%global with_php_json 1 %global with_php_json 1
%endif %endif
# el7 uses cmake3 package and macros
%if 0%{?rhel} == 7
%global cmake %{cmake3}
%global cmake_build %{cmake3_build}
%global cmake_install %{cmake3_install}
%global cmake_pkg_name cmake3
%else
%global cmake_pkg_name cmake
%endif
# The default for everything but el7 these days # The default for everything but el7 these days
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.36.4 Version: 1.37.1
Release: 1%{?dist} Release: 1%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons Group: System Environment/Daemons
@ -56,7 +61,7 @@ BuildRequires: systemd-devel
BuildRequires: mariadb-devel BuildRequires: mariadb-devel
BuildRequires: perl-podlators BuildRequires: perl-podlators
BuildRequires: polkit-devel BuildRequires: polkit-devel
BuildRequires: cmake3 BuildRequires: %{cmake_pkg_name}
BuildRequires: gnutls-devel BuildRequires: gnutls-devel
BuildRequires: bzip2-devel BuildRequires: bzip2-devel
BuildRequires: pcre-devel BuildRequires: pcre-devel
@ -116,8 +121,8 @@ Requires: php-mysqli
Requires: php-common Requires: php-common
Requires: php-gd Requires: php-gd
%{?with_php_json:Requires: php-json} %{?with_php_json:Requires: php-json}
Requires: php-pecl-apcu %{?fedora:Requires: php-pecl-memcached}
%{?with_apcu_bc:Requires: php-pecl-apcu-bc} %{?rhel:Requires: php-pecl-apcu}
Requires: cambozola Requires: cambozola
Requires: net-tools Requires: net-tools
Requires: psmisc Requires: psmisc
@ -216,16 +221,16 @@ mv -f RtspServer-%{rtspserver_commit} ./dep/RtspServer
# See https://fedoraproject.org/wiki/LTOByDefault # See https://fedoraproject.org/wiki/LTOByDefault
%define _lto_cflags %{nil} %define _lto_cflags %{nil}
%cmake3 \ %cmake \
-DZM_WEB_USER="%{zmuid_final}" \ -DZM_WEB_USER="%{zmuid_final}" \
-DZM_WEB_GROUP="%{zmgid_final}" \ -DZM_WEB_GROUP="%{zmgid_final}" \
-DZM_TARGET_DISTRO="%{zmtargetdistro}" \ -DZM_TARGET_DISTRO="%{zmtargetdistro}" \
. .
%cmake3_build %cmake_build
%install %install
%cmake3_install %cmake_install
desktop-file-install \ desktop-file-install \
--dir %{buildroot}%{_datadir}/applications \ --dir %{buildroot}%{_datadir}/applications \
@ -425,6 +430,16 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin
%dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder
%changelog %changelog
* Mon Jul 05 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.37.1-1
- 1.37.x development build
* Tue Jun 22 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.5-1
- 1.36.5 release
* Fri Jun 18 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.4-2
- apcu-bc deprecated on fedora, use memcached instead
- only refer to cmake3 when building on el7
* Tue Jun 08 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.4-1 * Tue Jun 08 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.4-1
- 1.36.4 release - 1.36.4 release

View File

@ -2,7 +2,7 @@ Source: zoneminder
Section: net Section: net
Priority: optional Priority: optional
Maintainer: Isaac Connor <isaac@zoneminder.com> Maintainer: Isaac Connor <isaac@zoneminder.com>
Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2
,cmake ,cmake
,libavcodec-dev ,libavcodec-dev
,libavformat-dev ,libavformat-dev
@ -30,7 +30,7 @@ Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-ap
,libcrypt-eksblowfish-perl ,libcrypt-eksblowfish-perl
,libdata-entropy-perl ,libdata-entropy-perl
,libvncserver-dev ,libvncserver-dev
,libjwt-gnutls-dev ,libjwt-gnutls-dev|libjwt-dev
Standards-Version: 4.5.0 Standards-Version: 4.5.0
Homepage: https://www.zoneminder.com/ Homepage: https://www.zoneminder.com/
@ -39,8 +39,8 @@ Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,sudo ,sudo
,javascript-common ,javascript-common
,libswscale5 ,libswscale5|libswscale4
,libswresample3 ,libswresample3|libswresample2
,ffmpeg ,ffmpeg
,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl
,libdbd-mysql-perl ,libdbd-mysql-perl
@ -74,7 +74,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libcrypt-eksblowfish-perl ,libcrypt-eksblowfish-perl
,libdata-entropy-perl ,libdata-entropy-perl
,libvncclient1|libvncclient0 ,libvncclient1|libvncclient0
,libjwt-gnutls0 ,libjwt-gnutls0|libjwt0
Recommends: ${misc:Recommends} Recommends: ${misc:Recommends}
,libapache2-mod-php | php-fpm ,libapache2-mod-php | php-fpm
,default-mysql-server | mariadb-server | virtual-mysql-server ,default-mysql-server | mariadb-server | virtual-mysql-server

View File

@ -1,4 +1,4 @@
d /run/zm 0755 www-data www-data d /run/zm 0755 www-data www-data
d /tmp/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data
d /var/tmp/zm 0755 www-data www-data d /var/tmp/zm 0755 www-data www-data 7d
d /var/cache/zoneminder/cache 0755 www-data www-data d /var/cache/zoneminder/cache 0755 www-data www-data

View File

@ -56,13 +56,13 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file
:: ::
# ZoneMinder repository # ZoneMinder repository
deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/ deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/
You can do this using: You can do this using:
.. code-block:: .. code-block::
echo "deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list
Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS. Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS.
:: ::
@ -158,7 +158,7 @@ You are now ready to go with ZoneMinder. Open a browser and type either ``localh
Easy Way: Debian Stretch Easy Way: Debian Stretch
------------------------ ------------------------
This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.34 on Debian 9.8. This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.36 on Debian 9.8.
**Step 1:** Make sure your system is up to date **Step 1:** Make sure your system is up to date
@ -204,7 +204,7 @@ Add the following to the bottom of the file
:: ::
# ZoneMinder repository # ZoneMinder repository
deb https://zmrepo.zoneminder.com/debian/release-1.34 stretch/ deb https://zmrepo.zoneminder.com/debian/release-1.36 stretch/
CTRL+o and <Enter> to save CTRL+o and <Enter> to save
CTRL+x to exit CTRL+x to exit

View File

@ -77,7 +77,7 @@ To start the build, simply execute the following command from the root folder of
OS=<distroname> DIST=<distrorel> utils/packpack/startpackpack.sh OS=<distroname> DIST=<distrorel> utils/packpack/startpackpack.sh
Where <distroname> is the name of the distro you wish to build on, such as fedora, and <distrorev> is release name or number of the distro you wish to build on. Redhat distros expect a number for <distrorev> while Debian and Ubuntu distros expect a name. For example: Where <distroname> is the name of the distro you wish to build on, such as fedora, and <distrorel> is the release name or number of the distro you wish to build on. Redhat distros expect a number for <distrorel> while Debian and Ubuntu distros expect a name. For example:
:: ::

View File

@ -41,7 +41,7 @@ guide you with a quick search.
:: ::
add-apt-repository ppa:iconnor/zoneminder-1.34 add-apt-repository ppa:iconnor/zoneminder-1.36
Update repo and upgrade. Update repo and upgrade.

View File

@ -6,7 +6,7 @@ Here are some options for using ZoneMinder on Mobile devices:
Third party mobile clients Third party mobile clients
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
* zmNinja (`source code <https://github.com/pliablepixels/zmNinja>`__, needs APIs to be installed to work) * zmNinja (`source code <https://github.com/pliablepixels/zmNinja>`__, needs APIs to be installed to work)
* Available in App Store, Play Store and for Desktops - `website <http://pliablepixels.github.io/zmNinja/>`__ * Available in App Store, Play Store and for Desktops - `website <http://pliablepixels.github.io/>`__
Using the existing web console Using the existing web console
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -17,4 +17,4 @@ Discontinued clients
The following are a list of clients that do not work and have not been updated: The following are a list of clients that do not work and have not been updated:
* eyeZM * eyeZM
* zmView * zmView

View File

@ -101,6 +101,6 @@ FROM_EMAIL - The emails or messages that will be sent to you informing you of ev
URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``. URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``.
SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page <http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder>`__ for setup and configuration help. SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page <https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder>`__ for setup and configuration help.
SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp.

View File

@ -5,10 +5,7 @@ This screen lets you customize several aspects of the web interface of ZoneMinde
.. image:: images/Options_web.png .. image:: images/Options_web.png
WEB_TITLE - WEB_TITLE - The actual text that is shown on the login screen. It is possible that it also appears in other areas.
.. todo ::
not quite sure what this does. Seems to change the "target" name - not sure what effect it is supposed to have.
WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification.
@ -48,4 +45,4 @@ WEB_USE_OBJECT_TAGS - There are two methods of including media content in web pa
WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off
WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results.

2
misc/fail2ban.rules Normal file
View File

@ -0,0 +1,2 @@
failregex = ^\s*web_php\[\d+\]\.ERR \[<HOST>\].*includes/auth.php
datepattern = ^%%m/%%d/%%y %%H:%%M:%%S(?:\.%%f)

View File

@ -308,7 +308,7 @@ our @options = (
}, },
{ {
name => 'ZM_AUTH_HASH_IPS', name => 'ZM_AUTH_HASH_IPS',
default => 'yes', default => 'no',
description => 'Include IP addresses in the authentication hash', description => 'Include IP addresses in the authentication hash',
help => q` help => q`
When ZoneMinder is running in hashed authenticated mode it can When ZoneMinder is running in hashed authenticated mode it can
@ -346,7 +346,7 @@ our @options = (
}, },
{ {
name => 'ZM_AUTH_HASH_LOGINS', name => 'ZM_AUTH_HASH_LOGINS',
default => 'no', default => 'yes',
description => 'Allow login by authentication hash', description => 'Allow login by authentication hash',
help => q` help => q`
The normal process for logging into ZoneMinder is via the login The normal process for logging into ZoneMinder is via the login
@ -508,7 +508,7 @@ our @options = (
}, },
{ {
name => 'ZM_SYSTEM_SHUTDOWN', name => 'ZM_SYSTEM_SHUTDOWN',
default => 'yes', default => 'no',
description => 'Allow Admin users to power off or restart the system from the ZoneMinder UI.', description => 'Allow Admin users to power off or restart the system from the ZoneMinder UI.',
help => 'The system will need to have sudo installed and the following added to /etc/sudoers~~ help => 'The system will need to have sudo installed and the following added to /etc/sudoers~~
~~ ~~
@ -1096,7 +1096,7 @@ our @options = (
}, },
{ {
name => 'ZM_LOG_LEVEL_SYSLOG', name => 'ZM_LOG_LEVEL_SYSLOG',
default => '0', default => '-1',
description => 'Save logging output to the system log', description => 'Save logging output to the system log',
help => q` help => q`
ZoneMinder logging is now more integrated between ZoneMinder logging is now more integrated between
@ -1604,7 +1604,7 @@ our @options = (
}, },
{ {
name => 'ZM_WEB_EVENT_DISK_SPACE', name => 'ZM_WEB_EVENT_DISK_SPACE',
default => 'no', default => 'yes',
description => 'Whether to show disk space used by each event.', description => 'Whether to show disk space used by each event.',
help => q` help => q`
Adds another column to the listing of events Adds another column to the listing of events
@ -1634,7 +1634,7 @@ our @options = (
}, },
{ {
name => 'ZM_WEB_ID_ON_CONSOLE', name => 'ZM_WEB_ID_ON_CONSOLE',
default => 'no', default => 'yes',
description => 'Should the console list the monitor id', description => 'Should the console list the monitor id',
help => q` help => q`
Some find it useful to have the id always visible Some find it useful to have the id always visible
@ -2270,7 +2270,7 @@ our @options = (
}, },
{ {
name => 'ZM_MAX_RESTART_DELAY', name => 'ZM_MAX_RESTART_DELAY',
default => '600', default => '30',
description => 'Maximum delay (in seconds) for daemon restart attempts.', description => 'Maximum delay (in seconds) for daemon restart attempts.',
help => q` help => q`
The zmdc (zm daemon control) process controls when processeses The zmdc (zm daemon control) process controls when processeses
@ -2855,7 +2855,7 @@ our @options = (
}, },
{ {
name => 'ZM_WEB_H_REFRESH_MAIN', name => 'ZM_WEB_H_REFRESH_MAIN',
default => '60', default => '240',
introduction => q` introduction => q`
There are now a number of options that are grouped into There are now a number of options that are grouped into
bandwidth categories, this allows you to configure the bandwidth categories, this allows you to configure the
@ -3113,7 +3113,7 @@ our @options = (
}, },
{ {
name => 'ZM_WEB_H_AJAX_TIMEOUT', name => 'ZM_WEB_H_AJAX_TIMEOUT',
default => '3000', default => '10000',
description => 'How long to wait for Ajax request responses (ms)', description => 'How long to wait for Ajax request responses (ms)',
help => q` help => q`
The newer versions of the live feed and event views use Ajax to The newer versions of the live feed and event views use Ajax to
@ -3766,7 +3766,7 @@ our @options = (
SSMTP is a lightweight and efficient method to send email. SSMTP is a lightweight and efficient method to send email.
The SSMTP application is not installed by default. The SSMTP application is not installed by default.
NEW_MAIL_MODULES must also be enabled. NEW_MAIL_MODULES must also be enabled.
Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder) Please visit the ZoneMinder [SSMTP Wiki page](https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder)
for setup and configuration help. for setup and configuration help.
`, `,
type => $types{boolean}, type => $types{boolean},

View File

@ -220,14 +220,14 @@ sub moveConUpRight {
my $self = shift; my $self = shift;
Debug('Move Diagonally Up Right'); Debug('Move Diagonally Up Right');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=RightUp&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
sub moveConDownRight { sub moveConDownRight {
my $self = shift; my $self = shift;
Debug('Move Diagonally Down Right'); Debug('Move Diagonally Down Right');
$$self{LastCmd} = 'code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=RightDown&channel=0&arg1=1&arg2=1&arg3=0';
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
@ -236,7 +236,7 @@ sub moveConUpLeft {
my $self = shift; my $self = shift;
Debug('Move Diagonally Up Left'); Debug('Move Diagonally Up Left');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
@ -244,7 +244,7 @@ sub moveConDownLeft {
my $self = shift; my $self = shift;
Debug('Move Diagonally Down Left'); Debug('Move Diagonally Down Left');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }

View File

@ -283,7 +283,7 @@ None by default.
=head1 SEE ALSO =head1 SEE ALSO
See if there are better instructions for the DCS-5020L at See if there are better instructions for the DCS-5020L at
http://www.zoneminder.com/wiki/index.php/Dlink https://wiki.zoneminder.com/Dlink
=head1 AUTHOR =head1 AUTHOR

View File

@ -1,6 +1,6 @@
# ========================================================================== # ==========================================================================
# #
# ZoneMinder ONVIF Control Protocol Module, $Date: 2021-02-25 22:07:00 +0000 (Thu, 25 Feb 2021) $, $Revision: 0001 $ # ZoneMinder ONVIF Control Protocol Module
# Based on the Netcat onvif script by Andrew Bauer (knnniggett@users.sourceforge.net) # Based on the Netcat onvif script by Andrew Bauer (knnniggett@users.sourceforge.net)
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
@ -182,6 +182,7 @@ sub sendCmd {
$req->header('connection' => 'Close'); $req->header('connection' => 'Close');
$req->content($msg); $req->content($msg);
my $res = $self->{ua}->request($req); my $res = $self->{ua}->request($req);
if ( $res->is_success ) { if ( $res->is_success ) {
@ -235,22 +236,39 @@ sub getCamParams {
sub autoStop { sub autoStop {
my $self = shift; my $self = shift;
my $autostop = shift; my $autostop = shift;
my $iszoom = shift;
if ( $autostop ) { if ( $autostop ) {
Debug('Auto Stop'); Debug('Auto Stop');
my $cmd = $controlUri; my $cmd = $controlUri;
my $msg_body = ' my $msg_body;
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> if( $iszoom) {
<Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"> $msg_body = '
<ProfileToken>'.$profileToken.'</ProfileToken> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PanTilt> <Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl">
true <ProfileToken>'.$profileToken.'</ProfileToken>
</PanTilt> <PanTilt>
<Zoom> false
false </PanTilt>
</Zoom> <Zoom>
</Stop> true
</s:Body>'; </Zoom>
</Stop>
</s:Body>';
} else {
$msg_body = '
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl">
<ProfileToken>'.$profileToken.'</ProfileToken>
<PanTilt>
true
</PanTilt>
<Zoom>
false
</Zoom>
</Stop>
</s:Body>';
}
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
usleep($autostop); usleep($autostop);
@ -340,9 +358,9 @@ sub moveCamera {
my $type = shift; my $type = shift;
my $x = shift; my $x = shift;
my $y = shift; my $y = shift;
my $msg_move_body = ""; my $msg_move_body = '';
if ( $type == "move" ){ if ( $type eq 'move' ) {
$msg_move_body = ' $msg_move_body = '
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"> <ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl">
@ -356,7 +374,7 @@ sub moveCamera {
</ContinuousMove> </ContinuousMove>
</s:Body>'; </s:Body>';
} elsif ( $type == "zoom" ) { } elsif ( $type eq 'zoom' ) {
$msg_move_body = ' $msg_move_body = '
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"> <ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl">
@ -371,7 +389,6 @@ sub moveCamera {
} }
return $msg_move_body; return $msg_move_body;
} }
#Up Arrow #Up Arrow
@ -382,7 +399,7 @@ sub moveConUp {
my $msg_body = moveCamera("move", "0","0.5"); my $msg_body = moveCamera("move", "0","0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
@ -394,7 +411,7 @@ sub moveConDown {
my $msg_body = moveCamera("move","0","-0.5"); my $msg_body = moveCamera("move","0","-0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Left Arrow #Left Arrow
@ -405,7 +422,7 @@ sub moveConLeft {
my $msg_body = moveCamera("move","-0.49","0"); my $msg_body = moveCamera("move","-0.49","0");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Right Arrow #Right Arrow
@ -416,7 +433,7 @@ sub moveConRight {
my $msg_body = moveCamera("move","0.49","0"); my $msg_body = moveCamera("move","0.49","0");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Zoom In #Zoom In
@ -427,7 +444,7 @@ sub zoomConTele {
my $msg_body = moveCamera("zoom","0.49","0"); my $msg_body = moveCamera("zoom","0.49","0");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},1);
} }
#Zoom Out #Zoom Out
@ -438,9 +455,19 @@ sub zoomConWide {
my $msg_body = moveCamera("zoom","-0.49","0"); my $msg_body = moveCamera("zoom","-0.49","0");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},1);
} }
sub zoomStop {
Debug('Zoom Stop');
my $self = shift;
my $cmd = $controlUri;
$self->autoStop($self->{Monitor}->{AutoStopTimeout},1);
Error('Zoom Stop not implemented');
}
#Diagonally Up Right Arrow #Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them #This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight { sub moveConUpRight {
@ -450,7 +477,7 @@ sub moveConUpRight {
my $msg_body = moveCamera("move","0.5","0.5"); my $msg_body = moveCamera("move","0.5","0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Diagonally Down Right Arrow #Diagonally Down Right Arrow
@ -462,7 +489,7 @@ sub moveConDownRight {
my $msg_body = moveCamera("move","0.5","-0.5"); my $msg_body = moveCamera("move","0.5","-0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Diagonally Up Left Arrow #Diagonally Up Left Arrow
@ -474,7 +501,7 @@ sub moveConUpLeft {
my $msg_body = moveCamera("move","-0.5","0.5"); my $msg_body = moveCamera("move","-0.5","0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Diagonally Down Left Arrow #Diagonally Down Left Arrow
@ -486,7 +513,7 @@ sub moveConDownLeft {
my $msg_body = moveCamera("move","-0.5","-0.5"); my $msg_body = moveCamera("move","-0.5","-0.5");
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd($cmd, $msg_body, $content_type); $self->sendCmd($cmd, $msg_body, $content_type);
$self->autoStop($self->{Monitor}->{AutoStopTimeout}); $self->autoStop($self->{Monitor}->{AutoStopTimeout},0);
} }
#Stop #Stop
@ -499,7 +526,7 @@ sub moveStop {
<Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"> <Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl">
<ProfileToken>'.$profileToken.'</ProfileToken> <ProfileToken>'.$profileToken.'</ProfileToken>
<PanTilt>true</PanTilt> <PanTilt>true</PanTilt>
<Zoom>false</Zoom> <Zoom>true</Zoom>
</Stop> </Stop>
</s:Body>'; </s:Body>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';

View File

@ -107,6 +107,7 @@ sub zmDbConnect {
.$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '') .$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
, $ZoneMinder::Config::Config{ZM_DB_USER} , $ZoneMinder::Config::Config{ZM_DB_USER}
, $ZoneMinder::Config::Config{ZM_DB_PASS} , $ZoneMinder::Config::Config{ZM_DB_PASS}
, { mysql_enable_utf8 => 1, }
); );
}; };
if ( !$dbh or $@ ) { if ( !$dbh or $@ ) {

View File

@ -127,9 +127,11 @@ sub Execute {
foreach my $term ( @{$$self{PostSQLConditions}} ) { foreach my $term ( @{$$self{PostSQLConditions}} ) {
if ( $$term{attr} eq 'ExistsInFileSystem' ) { if ( $$term{attr} eq 'ExistsInFileSystem' ) {
foreach my $row ( @results ) { foreach my $row ( @results ) {
my $event = new ZoneMinder::Event($row); my $event = new ZoneMinder::Event($$row{Id}, $row);
if ( -e $event->Path() ) { if ( -e $event->Path() ) {
push @filtered_events, $row; push @filtered_events, $row if $$term{val} eq 'true';
} else {
push @filtered_events, $row if $$term{val} eq 'false';
} }
} }
} }
@ -164,138 +166,142 @@ sub Sql {
if ( exists($term->{obr}) ) { if ( exists($term->{obr}) ) {
$self->{Sql} .= str_repeat('(', $term->{obr}).' '; $self->{Sql} .= str_repeat('(', $term->{obr}).' ';
} }
if (!$term->{attr}) {
Error("Invalid term in filter $$self{Id}. Empty attr");
next;
}
my $value = $term->{val}; my $value = $term->{val};
my @value_list; my @value_list;
if ( $term->{attr} ) {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$term->{op} = 'EXISTS';
} elsif ( $term->{attr} =~ /^Monitor/ ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
$self->{Sql} .= 'M.ServerId';
} elsif ( $term->{attr} eq 'StorageServerId' ) {
$self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)';
} elsif ( $term->{attr} eq 'FilterServerId' ) {
$self->{Sql} .= $Config{ZM_SERVER_ID};
# StartTime options
} elsif ( $term->{attr} eq 'DateTime' ) {
$self->{Sql} .= 'E.StartDateTime';
} elsif ( $term->{attr} eq 'Date' ) {
$self->{Sql} .= 'to_days( E.StartDateTime )';
} elsif ( $term->{attr} eq 'StartDate' ) {
$self->{Sql} .= 'to_days( E.StartDateTime )';
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) {
$self->{Sql} .= 'extract( hour_second from E.StartDateTime )';
} elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) {
$self->{Sql} .= 'weekday( E.StartDateTime )';
# EndTIme options if ( $term->{attr} eq 'AlarmedZoneId' ) {
} elsif ( $term->{attr} eq 'EndDateTime' ) { $term->{op} = 'EXISTS';
$self->{Sql} .= 'E.EndDateTime'; } elsif ( $term->{attr} =~ /^Monitor/ ) {
} elsif ( $term->{attr} eq 'EndDate' ) { $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
$self->{Sql} .= 'to_days( E.EndDateTime )'; FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
} elsif ( $term->{attr} eq 'EndDateTime' ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; $self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'EndWeekday' ) { } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
$self->{Sql} .= "weekday( E.EndDateTime )"; $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
} elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
push @{$self->{PostSQLConditions}}, $term; $self->{Sql} .= 'M.ServerId';
$self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; } elsif ( $term->{attr} eq 'StorageServerId' ) {
} elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)';
$self->{Sql} .= 'zmDiskPercent'; } elsif ( $term->{attr} eq 'FilterServerId' ) {
$self->{HasDiskPercent} = !undef; $self->{Sql} .= $Config{ZM_SERVER_ID};
} elsif ( $term->{attr} eq 'DiskBlocks' ) { # StartTime options
$self->{Sql} .= 'zmDiskBlocks'; } elsif ( $term->{attr} eq 'DateTime' ) {
$self->{HasDiskBlocks} = !undef; $self->{Sql} .= 'E.StartDateTime';
} elsif ( $term->{attr} eq 'SystemLoad' ) { } elsif ( $term->{attr} eq 'Date' ) {
$self->{Sql} .= 'zmSystemLoad'; $self->{Sql} .= 'to_days( E.StartDateTime )';
$self->{HasSystemLoad} = !undef; } elsif ( $term->{attr} eq 'StartDate' ) {
} else { $self->{Sql} .= 'to_days( E.StartDateTime )';
$self->{Sql} .= 'E.'.$term->{attr}; } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) {
} $self->{Sql} .= 'extract( hour_second from E.StartDateTime )';
} elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) {
$self->{Sql} .= 'weekday( E.StartDateTime )';
if ( $term->{attr} eq 'ExistsInFileSystem' ) { # EndTIme options
# PostCondition, so no further SQL } elsif ( $term->{attr} eq 'EndDateTime' ) {
} else { $self->{Sql} .= 'E.EndDateTime';
( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; } elsif ( $term->{attr} eq 'EndDate' ) {
foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { $self->{Sql} .= 'to_days( E.EndDateTime )';
} elsif ( $term->{attr} eq 'EndTime' ) {
$self->{Sql} .= 'extract( hour_second from E.EndDateTime )';
} elsif ( $term->{attr} eq 'EndWeekday' ) {
$self->{Sql} .= 'weekday( E.EndDateTime )';
} elsif ( $term->{attr} eq 'ExistsInFileSystem' ) {
push @{$self->{PostSQLConditions}}, $term;
$self->{Sql} .= 'TRUE /* ExistsInFileSystem */';
} elsif ( $term->{attr} eq 'DiskPercent' ) {
$self->{Sql} .= 'zmDiskPercent';
$self->{HasDiskPercent} = !undef;
} elsif ( $term->{attr} eq 'DiskBlocks' ) {
$self->{Sql} .= 'zmDiskBlocks';
$self->{HasDiskBlocks} = !undef;
} elsif ( $term->{attr} eq 'SystemLoad' ) {
$self->{Sql} .= 'zmSystemLoad';
$self->{HasSystemLoad} = !undef;
} else {
$self->{Sql} .= 'E.'.$term->{attr};
}
if ( $term->{attr} eq 'AlarmedZoneId' ) { if ( $term->{attr} eq 'ExistsInFileSystem' ) {
$value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; # PostCondition, so no further SQL
} elsif ( $term->{attr} =~ /^MonitorName/ ) { } else {
$value = "'$temp_value'"; ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
} elsif ( $term->{attr} =~ /ServerId/) { foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) {
Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})");
if ( $temp_value eq 'ZM_SERVER_ID' ) { if ( $term->{attr} eq 'AlarmedZoneId' ) {
$value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')';
# This gets used later, I forget for what } elsif ( $term->{attr} =~ /^MonitorName/ ) {
$$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); $value = "'$temp_value'";
} elsif ( $temp_value eq 'NULL' ) { } elsif ( $term->{attr} =~ /ServerId/) {
$value = $temp_value; Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})");
} else { if ( $temp_value eq 'ZM_SERVER_ID' ) {
$value = "'$temp_value'"; $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'";
# This gets used later, I forget for what # This gets used later, I forget for what
$$self{Server} = new ZoneMinder::Server($temp_value); $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID});
} } elsif ( $temp_value eq 'NULL' ) {
} elsif ( $term->{attr} eq 'StorageId' ) {
$value = "'$temp_value'";
$$self{Storage} = new ZoneMinder::Storage($temp_value);
} elsif ( $term->{attr} eq 'Name'
|| $term->{attr} eq 'Cause'
|| $term->{attr} eq 'Notes'
) {
if ( $term->{op} eq 'LIKE'
|| $term->{op} eq 'NOT LIKE'
) {
$temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/;
}
$value = "'$temp_value'";
} elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "'$value'";
}
} elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) {
$value = 'to_days('.$temp_value.')';
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "to_days( '$value' )";
}
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "extract( hour_second from '$value' )";
}
} else {
$value = $temp_value; $value = $temp_value;
} else {
$value = "'$temp_value'";
# This gets used later, I forget for what
$$self{Server} = new ZoneMinder::Server($temp_value);
} }
push @value_list, $value; } elsif ( $term->{attr} eq 'StorageId' ) {
} # end foreach temp_value $value = "'$temp_value'";
} # end if has an attr $$self{Storage} = new ZoneMinder::Storage($temp_value);
} elsif ( $term->{attr} eq 'Name'
|| $term->{attr} eq 'Cause'
|| $term->{attr} eq 'Notes'
) {
if ( $term->{op} eq 'LIKE'
|| $term->{op} eq 'NOT LIKE'
) {
$temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/;
}
$value = "'$temp_value'";
} elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "'$value'";
}
} elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) {
$value = 'to_days('.$temp_value.')';
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "to_days( '$value' )";
}
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "extract( hour_second from '$value' )";
}
} else {
$value = $temp_value;
}
push @value_list, $value;
} # end foreach temp_value
if ( $term->{op} ) { if ( $term->{op} ) {
if ( $term->{op} eq '=~' ) { if ( $term->{op} eq '=~' ) {

View File

@ -136,8 +136,8 @@ $serial = $primary_key = 'Id';
%defaults = ( %defaults = (
ServerId => 0, ServerId => 0,
StorageId => 0, StorageId => 0,
Type => 'Ffmpeg', Type => q`'Ffmpeg'`,
Function => 'Mocord', Function => q`'Mocord'`,
Enabled => 1, Enabled => 1,
LinkedMonitors => undef, LinkedMonitors => undef,
Device => '', Device => '',
@ -166,15 +166,15 @@ $serial = $primary_key = 'Id';
VideoWriter => 0, VideoWriter => 0,
OutputCodec => undef, OutputCodec => undef,
OutputContainer => undef, OutputContainer => undef,
EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", EncoderParameters => '',
RecordAudio=>0, RecordAudio=>0,
RTSPDescribe=>0, RTSPDescribe=>0,
Brightness => -1, Brightness => -1,
Contrast => -1, Contrast => -1,
Hue => -1, Hue => -1,
Colour => -1, Colour => -1,
EventPrefix => 'Event-', EventPrefix => q`'Event-'`,
LabelFormat => '%N - %d/%m/%y %H:%M:%S', LabelFormat => '',
LabelX => 0, LabelX => 0,
LabelY => 0, LabelY => 0,
LabelSize => 1, LabelSize => 1,
@ -208,13 +208,13 @@ $serial = $primary_key = 'Id';
DefaultRate => 100, DefaultRate => 100,
DefaultScale => 100, DefaultScale => 100,
SignalCheckPoints => 0, SignalCheckPoints => 0,
SignalCheckColour => '#0000BE', SignalCheckColour => q`'#0000BE'`,
WebColour => '#ff0000', WebColour => q`'#ff0000'`,
Exif => 0, Exif => 0,
Sequence => undef, Sequence => undef,
ZoneCount => 0, ZoneCount => 0,
Refresh => undef, Refresh => undef,
DefaultCodec => 'auto', DefaultCodec => q`'auto'`,
Latitude => undef, Latitude => undef,
Longitude => undef, Longitude => undef,
); );
@ -279,6 +279,7 @@ sub disconnect {
sub suspendMotionDetection { sub suspendMotionDetection {
my $self = shift; my $self = shift;
return 0 if ! ZoneMinder::Memory::zmMemVerify($self); return 0 if ! ZoneMinder::Memory::zmMemVerify($self);
return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None';
my $count = 50; my $count = 50;
while ($count and ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { while ($count and ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) {
ZoneMinder::Logger::Debug(1, 'Suspending motion detection'); ZoneMinder::Logger::Debug(1, 'Suspending motion detection');
@ -286,19 +287,29 @@ sub suspendMotionDetection {
usleep(100000); usleep(100000);
$count -= 1; $count -= 1;
} }
ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.') if !$count; if (!$count) {
ZoneMinder::Logger::Debug(1, ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)); ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.');
ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end
} else {
ZoneMinder::Logger::Debug(1, 'shared_data:active='.ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1));
}
} }
sub resumeMotionDetection { sub resumeMotionDetection {
my $self = shift; my $self = shift;
return 0 if ! ZoneMinder::Memory::zmMemVerify($self); return 0 if ! ZoneMinder::Memory::zmMemVerify($self);
return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None';
my $count = 50; my $count = 50;
while ($count and !ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { while ($count and !ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) {
ZoneMinder::Logger::Debug(1, 'Resuming motion detection'); ZoneMinder::Logger::Debug(1, 'Resuming motion detection');
ZoneMinder::Memory::zmMonitorResume($self); ZoneMinder::Memory::zmMonitorResume($self);
usleep(100000);
$count -= 1;
}
if (!$count) {
ZoneMinder::Logger::Error('Unable to resume motion detection after 5 seconds.');
ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end
} }
ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.') if !$count;
return 1; return 1;
} }

View File

@ -40,6 +40,10 @@ $serial = $primary_key = 'Id';
MonitorId MonitorId
Type Type
Units Units
NumCoords
Coords
Area
AlarmRGB
CheckMethod CheckMethod
MinPixelThreshold MinPixelThreshold
MaxPixelThreshold MaxPixelThreshold
@ -59,9 +63,13 @@ $serial = $primary_key = 'Id';
%defaults = ( %defaults = (
Name => '', Name => '',
Type => 'Active', Type => q`'Active'`,
Units => 'Pixels', Units => q`'Pixels'`,
CheckMethod => 'Blobs', NumCoords => 0,
Coords => '',
Area => 0,
AlarmRGB => 0,
CheckMethod => q`'Blobs'`,
MinPixelThreshold => undef, MinPixelThreshold => undef,
MaxPixelThreshold => undef, MaxPixelThreshold => undef,
MinAlarmPixels => undef, MinAlarmPixels => undef,

View File

@ -61,12 +61,12 @@ GetOptions(
'autostop' =>\$options{autostop}, 'autostop' =>\$options{autostop},
) or pod2usage(-exitstatus => -1); ) or pod2usage(-exitstatus => -1);
if ( !$id ) { if (!$id) {
print(STDERR "Please give a valid monitor id\n"); print(STDERR "Please give a valid monitor id\n");
pod2usage(-exitstatus => -1); pod2usage(-exitstatus => -1);
} }
( $id ) = $id =~ /^(\w+)$/; ($id) = $id =~ /^(\w+)$/;
logInit($id?(id=>'zmcontrol_'.$id):()); logInit($id?(id=>'zmcontrol_'.$id):());
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
@ -76,7 +76,7 @@ socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!");
my $saddr = sockaddr_un($sock_file); my $saddr = sockaddr_un($sock_file);
if ( $options{command} ) { if ($options{command}) {
# Have a command, so we are the client, connect to the server and send it. # Have a command, so we are the client, connect to the server and send it.
my $tries = 10; my $tries = 10;
@ -101,18 +101,16 @@ if ( $options{command} ) {
Error("Unable to connect to zmcontrol server at $sock_file"); Error("Unable to connect to zmcontrol server at $sock_file");
} }
} else { } else {
# The server isn't there # The server isn't there
my $monitor = zmDbGetMonitorAndControl($id); my $monitor = zmDbGetMonitorAndControl($id);
if ( !$monitor ) { Fatal("Unable to load control data for monitor $id") if !$monitor;
Fatal("Unable to load control data for monitor $id");
}
my $protocol = $monitor->{Protocol}; my $protocol = $monitor->{Protocol};
if ( !$protocol ) { if (!$protocol) {
Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field');
} }
if ( -x $protocol ) { if (-x $protocol) {
# Protocol is actually a script! # Protocol is actually a script!
# Holdover from previous versions # Holdover from previous versions
my $command .= $protocol.' '.$arg_string; my $command .= $protocol.' '.$arg_string;
@ -120,11 +118,11 @@ if ( $options{command} ) {
my $output = qx($command); my $output = qx($command);
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ($status || logDebugging()) {
chomp($output); chomp($output);
Debug("Output: $output"); Debug("Output: $output");
} }
if ( $status ) { if ($status) {
Error("Command '$command' exited with status: $status"); Error("Command '$command' exited with status: $status");
exit($status); exit($status);
} }
@ -134,7 +132,7 @@ if ( $options{command} ) {
Info("Starting control server $id/$protocol"); Info("Starting control server $id/$protocol");
close(CLIENT); close(CLIENT);
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) {
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
} }
@ -159,7 +157,7 @@ if ( $options{command} ) {
$control->open(); $control->open();
# If we have a command when starting up, then do it. # If we have a command when starting up, then do it.
if ( $options{command} ) { if ($options{command}) {
my $command = $options{command}; my $command = $options{command};
$control->$command(\%options); $control->$command(\%options);
} }

View File

@ -399,7 +399,6 @@ sub checkFilter {
) { ) {
$Event->save(); $Event->save();
} }
$ZoneMinder::Database::dbh->commit() if !$$filter{LockRows};
} # end if UpdateDiskSpace } # end if UpdateDiskSpace
} # end foreach event } # end foreach event
ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows};
@ -527,7 +526,7 @@ sub uploadArchFile {
return(0); return(0);
} }
my $archFile = $Event->{MonitorName}.'-'.$Event->{Id}; my $archFile = $Event->Monitor()->Name().'-'.$Event->{Id};
my $archImagePath = $Event->Path() my $archImagePath = $Event->Path()
.'/' .'/'
.( .(
@ -548,6 +547,10 @@ sub uploadArchFile {
my $status = &AZ_OK; my $status = &AZ_OK;
foreach my $imageFile ( @archImageFiles ) { foreach my $imageFile ( @archImageFiles ) {
if (! -e $imageFile) {
Debug("Not adding $imageFile because it doesn't exist");
next;
}
Debug("Adding $imageFile"); Debug("Adding $imageFile");
my $member = $zip->addFile($imageFile); my $member = $zip->addFile($imageFile);
if ( !$member ) { if ( !$member ) {
@ -839,7 +842,7 @@ sub sendEmail {
return 0; return 0;
} }
Info('Creating notification email'); Debug('Creating notification email');
my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event); my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event);
return 0 if !$subject; return 0 if !$subject;
@ -847,7 +850,7 @@ sub sendEmail {
my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments); my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments);
return 0 if !$body; return 0 if !$body;
Info("Sending notification email '$subject'"); Debug("Sending notification email '$subject'");
eval { eval {
if ( $Config{ZM_NEW_MAIL_MODULES} ) { if ( $Config{ZM_NEW_MAIL_MODULES} ) {
@ -860,7 +863,7 @@ sub sendEmail {
); );
### Add the text message part ### Add the text message part
$mail->attach ( $mail->attach (
Type => 'TEXT', Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body Data => $body
); );
### Add the attachments ### Add the attachments
@ -882,9 +885,7 @@ sub sendEmail {
if ( $Config{ZM_SSMTP_MAIL} ) { if ( $Config{ZM_SSMTP_MAIL} ) {
my $ssmtp_location = $Config{ZM_SSMTP_PATH}; my $ssmtp_location = $Config{ZM_SSMTP_PATH};
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
if ( logDebugging() ) { Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message");
Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message");
}
$ssmtp_location = qx('which ssmtp'); $ssmtp_location = qx('which ssmtp');
} }
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
@ -912,7 +913,7 @@ sub sendEmail {
foreach my $attachment ( @attachments ) { foreach my $attachment ( @attachments ) {
my $size = -s $attachment->{path}; my $size = -s $attachment->{path};
$total_size += $size; $total_size += $size;
Info("Attaching '$attachment->{path}' which is $size bytes"); Debug("Attaching '$attachment->{path}' which is $size bytes");
$mail->attach( $mail->attach(
Path => $attachment->{path}, Path => $attachment->{path},
@ -930,7 +931,7 @@ sub sendEmail {
Error("Unable to send email: $@"); Error("Unable to send email: $@");
return 0; return 0;
} else { } else {
Info('Notification email sent'); Info("Notification email sent to $$filter{EmailTo}");
} }
my $sql = 'UPDATE `Events` SET `Emailed` = 1 WHERE `Id` = ?'; my $sql = 'UPDATE `Events` SET `Emailed` = 1 WHERE `Id` = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)

View File

@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database
=head1 SYNOPSIS =head1 SYNOPSIS
zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u<dbuser> -p<dbpass>] zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u <dbuser> -p <dbpass>]
=head1 DESCRIPTION =head1 DESCRIPTION

View File

@ -98,19 +98,19 @@ while (!$zm_terminate) {
next if $monitor->{Type} eq 'WebSite'; next if $monitor->{Type} eq 'WebSite';
my $now = time(); my $now = time();
my $restart = 0; my $restart = 0;
if ( zmMemVerify($monitor) ) { if (zmMemVerify($monitor)) {
# Check we have got an image recently # Check we have got an image recently
my $capture_time = zmGetLastWriteTime($monitor); my $capture_time = zmGetLastWriteTime($monitor);
if ( !defined($capture_time) ) { if (!defined($capture_time)) {
# Can't read from shared data # Can't read from shared data
Debug('LastWriteTime is not defined.'); Debug('LastWriteTime is not defined.');
zmMemInvalidate($monitor); zmMemInvalidate($monitor);
next; next;
} }
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if ( !$capture_time ) { if (!$capture_time) {
my $startup_time = zmGetStartupTime($monitor); my $startup_time = zmGetStartupTime($monitor);
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) { if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) {
Warning( Warning(
"Restarting capture daemon for $$monitor{Name}, no image since startup. ". "Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
@ -122,7 +122,7 @@ while (!$zm_terminate) {
next; next;
} }
} }
if ( ! $restart ) { if (!$restart) {
my $max_image_delay = ( my $max_image_delay = (
$monitor->{MaxFPS} $monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}>0)
@ -144,29 +144,28 @@ while (!$zm_terminate) {
$restart = 1; $restart = 1;
} }
if ( $restart ) { if ($restart) {
my $command; my $command;
if ( $monitor->{Type} eq 'Local' ) { if ($monitor->{Type} eq 'Local') {
$command = "zmdc.pl restart zmc -d $monitor->{Device}"; $command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else { } else {
$command = "zmdc.pl restart zmc -m $monitor->{Id}"; $command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
} }
runCommand($command); runCommand($command);
} elsif ( $monitor->{Function} ne 'Monitor' ) { } elsif ($monitor->{Function} ne 'Monitor') {
# Now check analysis daemon # Now check analysis daemon
$restart = 0; $restart = 0;
# Check we have got an image recently # Check we have got an image recently
my $image_time = zmGetLastReadTime($monitor); my $image_time = zmGetLastReadTime($monitor);
if ( !defined($image_time) ) { if (!defined($image_time)) {
# Can't read from shared data # Can't read from shared data
$restart = 1; $restart = 1;
Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); Error("Error reading shared data for $$monitor{Id} $$monitor{Name}");
} elsif ( !$image_time ) { } elsif (!$image_time) {
# We can't get the last capture time so can't be sure it's died. # We can't get the last capture time so can't be sure it's died.
#$restart = 1; #$restart = 1;
Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.");
} else { } else {
my $max_image_delay = ( $monitor->{MaxFPS} my $max_image_delay = ( $monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1) &&($monitor->{MaxFPS}<1)
@ -175,7 +174,7 @@ while (!$zm_terminate) {
; ;
my $image_delay = $now-$image_time; my $image_delay = $now-$image_time;
Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay"); Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay");
if ( $image_delay > $max_image_delay ) { if ($image_delay > $max_image_delay) {
Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
." time since last analysis $image_delay seconds ($now-$image_time)" ." time since last analysis $image_delay seconds ($now-$image_time)"
); );
@ -183,13 +182,13 @@ while (!$zm_terminate) {
} }
} }
if ( $restart ) { if ($restart) {
Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}");
my $command; my $command;
if ( $monitor->{Type} eq 'Local' ) { if ( $monitor->{Type} eq 'Local' ) {
$command = "zmdc.pl restart zmc -d $monitor->{Device}"; $command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else { } else {
$command = "zmdc.pl restart zmc -m $monitor->{Id}"; $command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
} }
runCommand($command); runCommand($command);
} # end if restart } # end if restart
@ -201,7 +200,7 @@ while (!$zm_terminate) {
sleep($Config{ZM_WATCH_CHECK_INTERVAL}); sleep($Config{ZM_WATCH_CHECK_INTERVAL});
} # end while (!$zm_terminate) } # end while (!$zm_terminate)
Info("Watchdog exiting"); Info('Watchdog exiting');
exit(); exit();
1; 1;

View File

@ -98,6 +98,12 @@ elseif(${ZM_JWT_BACKEND} STREQUAL "libjwt")
JWT::libjwt) JWT::libjwt)
endif() endif()
if(TARGET V4L2::videodev2)
target_link_libraries(zm
PRIVATE
V4L2::videodev2)
endif()
add_executable(zmc zmc.cpp) add_executable(zmc zmc.cpp)
add_executable(zms zms.cpp) add_executable(zms zms.cpp)
add_executable(zmu zmu.cpp) add_executable(zmu zmu.cpp)

View File

@ -74,11 +74,13 @@ int Buffer::read_into(int sd, unsigned int bytes) {
return bytes_read; return bytes_read;
} }
int Buffer::read_into(int sd, unsigned int bytes, struct timeval timeout) { int Buffer::read_into(int sd, unsigned int bytes, Microseconds timeout) {
fd_set set; fd_set set;
FD_ZERO(&set); /* clear the set */ FD_ZERO(&set); /* clear the set */
FD_SET(sd, &set); /* add our file descriptor to the set */ FD_SET(sd, &set); /* add our file descriptor to the set */
int rv = select(sd + 1, &set, NULL, NULL, &timeout); timeval timeout_tv = zm::chrono::duration_cast<timeval>(timeout);
int rv = select(sd + 1, &set, nullptr, nullptr, &timeout_tv);
if (rv == -1) { if (rv == -1) {
Error("Error %d %s from select", errno, strerror(errno)); Error("Error %d %s from select", errno, strerror(errno));
return rv; return rv;
@ -86,5 +88,6 @@ int Buffer::read_into(int sd, unsigned int bytes, struct timeval timeout) {
Debug(1, "timeout"); /* a timeout occured */ Debug(1, "timeout"); /* a timeout occured */
return 0; return 0;
} }
return read_into(sd, bytes); return read_into(sd, bytes);
} }

View File

@ -21,6 +21,7 @@
#define ZM_BUFFER_H #define ZM_BUFFER_H
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_time.h"
#include <cstring> #include <cstring>
class Buffer { class Buffer {
@ -187,7 +188,7 @@ class Buffer {
return static_cast<int>(mSize); return static_cast<int>(mSize);
} }
int read_into(int sd, unsigned int bytes); int read_into(int sd, unsigned int bytes);
int read_into(int sd, unsigned int bytes, struct timeval timeout); int read_into(int sd, unsigned int bytes, Microseconds timeout);
}; };
#endif // ZM_BUFFER_H #endif // ZM_BUFFER_H

View File

@ -615,19 +615,7 @@ bool zm::TcpUnixServer::accept(TcpUnixSocket *&newSocket) {
return true; return true;
} }
void zm::Select::setTimeout(int timeout) { void zm::Select::setTimeout(Microseconds timeout) {
mTimeout.tv_sec = timeout;
mTimeout.tv_usec = 0;
mHasTimeout = true;
}
void zm::Select::setTimeout(double timeout) {
mTimeout.tv_sec = int(timeout);
mTimeout.tv_usec = suseconds_t((timeout - mTimeout.tv_sec) * 1000000.0);
mHasTimeout = true;
}
void zm::Select::setTimeout(timeval timeout) {
mTimeout = timeout; mTimeout = timeout;
mHasTimeout = true; mHasTimeout = true;
} }
@ -703,7 +691,7 @@ void zm::Select::clearWriters() {
} }
int zm::Select::wait() { int zm::Select::wait() {
timeval tempTimeout = mTimeout; timeval tempTimeout = zm::chrono::duration_cast<timeval>(mTimeout);
timeval *selectTimeout = mHasTimeout ? &tempTimeout : nullptr; timeval *selectTimeout = mHasTimeout ? &tempTimeout : nullptr;
fd_set rfds; fd_set rfds;

View File

@ -22,6 +22,7 @@
#include "zm_exception.h" #include "zm_exception.h"
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_time.h"
#include <cerrno> #include <cerrno>
#include <netdb.h> #include <netdb.h>
#include <set> #include <set>
@ -560,13 +561,9 @@ class Select {
typedef std::vector<CommsBase *> CommsList; typedef std::vector<CommsBase *> CommsList;
Select() : mHasTimeout(false), mMaxFd(-1) {} Select() : mHasTimeout(false), mMaxFd(-1) {}
explicit Select(timeval timeout) : mMaxFd(-1) { setTimeout(timeout); } explicit Select(Microseconds timeout) : mMaxFd(-1) { setTimeout(timeout); }
explicit Select(int timeout) : mMaxFd(-1) { setTimeout(timeout); }
explicit Select(double timeout) : mMaxFd(-1) { setTimeout(timeout); }
void setTimeout(int timeout); void setTimeout(Microseconds timeout);
void setTimeout(double timeout);
void setTimeout(timeval timeout);
void clearTimeout(); void clearTimeout();
void calcMaxFd(); void calcMaxFd();
@ -590,7 +587,7 @@ class Select {
CommsList mReadable; CommsList mReadable;
CommsList mWriteable; CommsList mWriteable;
bool mHasTimeout; bool mHasTimeout;
timeval mTimeout; Microseconds mTimeout;
int mMaxFd; int mMaxFd;
}; };

View File

@ -39,16 +39,15 @@ void zmLoadStaticConfig() {
// update the Config hash with those values // update the Config hash with those values
DIR *configSubFolder = opendir(ZM_CONFIG_SUBDIR); DIR *configSubFolder = opendir(ZM_CONFIG_SUBDIR);
if (configSubFolder) { // subfolder exists and is readable if (configSubFolder) { // subfolder exists and is readable
char glob_pattern[PATH_MAX] = ""; std::string glob_pattern = stringtf("%s/*.conf", ZM_CONFIG_SUBDIR);
snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.conf", ZM_CONFIG_SUBDIR);
glob_t pglob; glob_t pglob;
int glob_status = glob(glob_pattern, 0, nullptr, &pglob); int glob_status = glob(glob_pattern.c_str(), 0, nullptr, &pglob);
if (glob_status != 0) { if (glob_status != 0) {
if (glob_status < 0) { if (glob_status < 0) {
Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno));
} else { } else {
Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status);
} }
} else { } else {
for (unsigned int i = 0; i < pglob.gl_pathc; i++) { for (unsigned int i = 0; i < pglob.gl_pathc; i++) {
@ -75,7 +74,7 @@ void zmLoadDBConfig() {
std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'",
staticConfig.SERVER_NAME.c_str()); staticConfig.SERVER_NAME.c_str());
zmDbRow dbrow; zmDbRow dbrow;
if (dbrow.fetch(sql.c_str())) { if (dbrow.fetch(sql)) {
staticConfig.SERVER_ID = atoi(dbrow[0]); staticConfig.SERVER_ID = atoi(dbrow[0]);
} else { } else {
Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str()); Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str());
@ -87,7 +86,7 @@ void zmLoadDBConfig() {
std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID); std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID);
zmDbRow dbrow; zmDbRow dbrow;
if (dbrow.fetch(sql.c_str())) { if (dbrow.fetch(sql)) {
staticConfig.SERVER_NAME = std::string(dbrow[0]); staticConfig.SERVER_NAME = std::string(dbrow[0]);
} else { } else {
Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID); Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID);
@ -100,13 +99,10 @@ void zmLoadDBConfig() {
} }
} }
snprintf(staticConfig.capture_file_format, sizeof(staticConfig.capture_file_format), "%%s/%%0%dd-capture.jpg", staticConfig.capture_file_format = stringtf("%%s/%%0%dd-capture.jpg", config.event_image_digits);
config.event_image_digits); staticConfig.analyse_file_format = stringtf("%%s/%%0%dd-analyse.jpg", config.event_image_digits);
snprintf(staticConfig.analyse_file_format, sizeof(staticConfig.analyse_file_format), "%%s/%%0%dd-analyse.jpg", staticConfig.general_file_format = stringtf("%%s/%%0%dd-%%s", config.event_image_digits);
config.event_image_digits); staticConfig.video_file_format = "%s/%s";
snprintf(staticConfig.general_file_format, sizeof(staticConfig.general_file_format), "%%s/%%0%dd-%%s",
config.event_image_digits);
snprintf(staticConfig.video_file_format, sizeof(staticConfig.video_file_format), "%%s/%%s");
} }
void process_configfile(char const *configFile) { void process_configfile(char const *configFile) {

View File

@ -25,10 +25,6 @@
#include "zm_config_defines.h" #include "zm_config_defines.h"
#include <string> #include <string>
#if !defined(PATH_MAX)
#define PATH_MAX 1024
#endif
#define ZM_MAX_IMAGE_WIDTH 2048 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_WIDTH 2048 // The largest image we imagine ever handling
#define ZM_MAX_IMAGE_HEIGHT 1536 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_HEIGHT 1536 // The largest image we imagine ever handling
#define ZM_MAX_IMAGE_COLOURS 4 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_COLOURS 4 // The largest image we imagine ever handling
@ -38,11 +34,6 @@
#define ZM_SCALE_BASE 100 // The factor by which we bump up 'scale' to simulate FP #define ZM_SCALE_BASE 100 // The factor by which we bump up 'scale' to simulate FP
#define ZM_RATE_BASE 100 // The factor by which we bump up 'rate' to simulate FP #define ZM_RATE_BASE 100 // The factor by which we bump up 'rate' to simulate FP
#define ZM_SQL_BATCH_SIZE 50 // Limit the size of multi-row SQL statements
#define ZM_SQL_SML_BUFSIZ 256 // Size of SQL buffer
#define ZM_SQL_MED_BUFSIZ 1024 // Size of SQL buffer
#define ZM_SQL_LGE_BUFSIZ 8192 // Size of SQL buffer
#define ZM_NETWORK_BUFSIZ 32768 // Size of network buffer #define ZM_NETWORK_BUFSIZ 32768 // Size of network buffer
#define ZM_MAX_FPS 30 // The maximum frame rate we expect to handle #define ZM_MAX_FPS 30 // The maximum frame rate we expect to handle
@ -74,10 +65,10 @@ struct StaticConfig {
std::string PATH_LOGS; std::string PATH_LOGS;
std::string PATH_SWAP; std::string PATH_SWAP;
std::string PATH_ARP; std::string PATH_ARP;
char capture_file_format[PATH_MAX]; std::string capture_file_format;
char analyse_file_format[PATH_MAX]; std::string analyse_file_format;
char general_file_format[PATH_MAX]; std::string general_file_format;
char video_file_format[PATH_MAX]; std::string video_file_format;
}; };
extern StaticConfig staticConfig; extern StaticConfig staticConfig;

View File

@ -1,17 +1,17 @@
// //
// ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $ // ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $
// Copyright (C) 2001-2008 Philip Coombes // Copyright (C) 2001-2008 Philip Coombes
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License // modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2 // as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version. // of the License, or (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
@ -73,19 +73,35 @@ void bind_libcurl_symbols() {
*(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup"); *(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup");
} }
cURLCamera::cURLCamera( const Monitor* monitor, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned 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::cURLCamera(
Camera( monitor, 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 ), const Monitor* monitor,
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) const std::string &p_path,
const std::string &p_user,
const std::string &p_pass,
unsigned int p_width,
unsigned 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(monitor, 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) {
if ( capture ) {
Initialise(); Initialise();
} }
} }
cURLCamera::~cURLCamera() { cURLCamera::~cURLCamera() {
if ( capture ) { if (capture) {
Terminate(); Terminate();
} }
} }
@ -95,40 +111,40 @@ void cURLCamera::Initialise() {
content_type_match_len = strlen(content_type_match); content_type_match_len = strlen(content_type_match);
databuffer.expand(CURL_BUFFER_INITIAL_SIZE); databuffer.expand(CURL_BUFFER_INITIAL_SIZE);
bind_libcurl_symbols(); bind_libcurl_symbols();
/* cURL initialization */ /* cURL initialization */
CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet)); Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet));
dlclose(curl_lib); dlclose(curl_lib);
return; return;
} }
Debug(2,"libcurl version: %s", (*curl_version_f)()); Debug(2, "libcurl version: %s", (*curl_version_f)());
/* Create the shared data mutex */ /* Create the shared data mutex */
int nRet = pthread_mutex_init(&shareddata_mutex, nullptr); int nRet = pthread_mutex_init(&shareddata_mutex, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Shared data mutex creation failed: %s",strerror(nRet)); Error("Shared data mutex creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the data available condition variable */ /* Create the data available condition variable */
nRet = pthread_cond_init(&data_available_cond, nullptr); nRet = pthread_cond_init(&data_available_cond, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Data available condition variable creation failed: %s",strerror(nRet)); Error("Data available condition variable creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the request complete condition variable */ /* Create the request complete condition variable */
nRet = pthread_cond_init(&request_complete_cond, nullptr); nRet = pthread_cond_init(&request_complete_cond, nullptr);
if(nRet != 0) { if (nRet != 0) {
Error("Request complete condition variable creation failed: %s",strerror(nRet)); Error("Request complete condition variable creation failed: %s",strerror(nRet));
return; return;
} }
/* Create the thread */ /* Create the thread */
nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this); nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this);
if(nRet != 0) { if (nRet != 0) {
Error("Thread creation failed: %s",strerror(nRet)); Error("Thread creation failed: %s",strerror(nRet));
return; return;
} }
@ -151,18 +167,19 @@ void cURLCamera::Terminate() {
/* cURL cleanup */ /* cURL cleanup */
(*curl_global_cleanup_f)(); (*curl_global_cleanup_f)();
if(curl_lib) if (curl_lib)
dlclose(curl_lib); dlclose(curl_lib);
} }
int cURLCamera::PrimeCapture() { int cURLCamera::PrimeCapture() {
getVideoStream();
//Info( "Priming capture from %s", mPath.c_str() ); //Info( "Priming capture from %s", mPath.c_str() );
return 0; return 1;
} }
int cURLCamera::PreCapture() { int cURLCamera::PreCapture() {
// Nothing to do here // Nothing to do here
return( 0 ); return 1;
} }
int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) { int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
@ -178,10 +195,9 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Grab the mutex to ensure exclusive access to the shared data */ /* Grab the mutex to ensure exclusive access to the shared data */
lock(); lock();
while ( !frameComplete ) { while (!frameComplete) {
/* If the work thread did a reset, reset our local variables */ /* If the work thread did a reset, reset our local variables */
if ( bReset ) { if (bReset) {
SubHeadersParsingComplete = false; SubHeadersParsingComplete = false;
frame_content_length = 0; frame_content_length = 0;
frame_content_type.clear(); frame_content_type.clear();
@ -189,40 +205,40 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
bReset = false; bReset = false;
} }
if ( mode == MODE_UNSET ) { if (mode == MODE_UNSET) {
/* Don't have a mode yet. Sleep while waiting for data */ /* Don't have a mode yet. Sleep while waiting for data */
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
if ( nRet != 0 ) { if (nRet != 0) {
Error("Failed waiting for available data condition variable: %s",strerror(nRet)); Error("Failed waiting for available data condition variable: %s",strerror(nRet));
return -1; return -1;
} }
} }
if ( mode == MODE_STREAM ) { if (mode == MODE_STREAM) {
/* Subheader parsing */ /* Subheader parsing */
while( !SubHeadersParsingComplete && !need_more_data ) { while (!SubHeadersParsingComplete && !need_more_data) {
size_t crlf_start, crlf_end, crlf_size; size_t crlf_start, crlf_end, crlf_size;
std::string subheader; std::string subheader;
/* Check if the buffer contains something */ /* Check if the buffer contains something */
if ( databuffer.empty() ) { if (databuffer.empty()) {
/* Empty buffer, wait for data */ /* Empty buffer, wait for data */
need_more_data = true; need_more_data = true;
break; break;
} }
/* Find crlf start */ /* Find crlf start */
crlf_start = memcspn(reinterpret_cast<const char *>(databuffer.head()),"\r\n",databuffer.size()); crlf_start = memcspn(reinterpret_cast<const char *>(databuffer.head()),"\r\n",databuffer.size());
if ( crlf_start == databuffer.size() ) { if (crlf_start == databuffer.size()) {
/* Not found, wait for more data */ /* Not found, wait for more data */
need_more_data = true; need_more_data = true;
break; break;
} }
/* See if we have enough data for determining crlf length */ /* See if we have enough data for determining crlf length */
if ( databuffer.size() < crlf_start+5 ) { if (databuffer.size() < crlf_start+5) {
/* Need more data */ /* Need more data */
need_more_data = true; need_more_data = true;
break; break;
@ -233,18 +249,22 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
crlf_size = (crlf_start + crlf_end) - crlf_start; crlf_size = (crlf_start + crlf_end) - crlf_start;
/* Is this the end of a previous stream? (This is just before the boundary) */ /* Is this the end of a previous stream? (This is just before the boundary) */
if ( crlf_start == 0 ) { if (crlf_start == 0) {
databuffer.consume(crlf_size); databuffer.consume(crlf_size);
continue; continue;
} }
/* Check for invalid CRLF size */ /* Check for invalid CRLF size */
if ( crlf_size > 4 ) { if (crlf_size > 4) {
Error("Invalid CRLF length"); 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) */ /* 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) ) { 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 */ /* This is the last header */
SubHeadersParsingComplete = true; SubHeadersParsingComplete = true;
} }
@ -255,48 +275,56 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Advance the buffer past this one */ /* Advance the buffer past this one */
databuffer.consume(crlf_start+crlf_size); 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 */ /* Find where the data in this header starts */
size_t subheader_data_start = subheader.rfind(' '); size_t subheader_data_start = subheader.rfind(' ');
if ( subheader_data_start == std::string::npos ) { if (subheader_data_start == std::string::npos) {
subheader_data_start = subheader.find(':'); subheader_data_start = subheader.find(':');
} }
/* Extract the data into a string */ /* Extract the data into a string */
std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos); 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 */ /* Check the header */
if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) { if (strncasecmp(subheader.c_str(), content_length_match, content_length_match_len) == 0) {
/* Found the content-length header */ /* Found the content-length header */
frame_content_length = atoi(subheader_data.c_str()); frame_content_length = atoi(subheader_data.c_str());
Debug(6,"Got content-length subheader: %d",frame_content_length); Debug(6,"Got content-length subheader: %d",frame_content_length);
} else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) { } else if (strncasecmp(subheader.c_str(), content_type_match, content_type_match_len) == 0) {
/* Found the content-type header */ /* Found the content-type header */
frame_content_type = subheader_data; frame_content_type = subheader_data;
Debug(6,"Got content-type subheader: %s",frame_content_type.c_str()); Debug(6,"Got content-type subheader: %s", frame_content_type.c_str());
} }
} }
/* Attempt to extract the frame */ /* Attempt to extract the frame */
if(!need_more_data) { if (!need_more_data) {
if(!SubHeadersParsingComplete) { if (!SubHeadersParsingComplete) {
/* We haven't parsed all headers yet */ /* We haven't parsed all headers yet */
need_more_data = true; need_more_data = true;
} else if ( ! frame_content_length ) { } else if (!frame_content_length) {
/* Invalid frame */ /* Invalid frame */
Error("Invalid frame: invalid content length"); Error("Invalid frame: invalid content length");
} else if ( frame_content_type != "image/jpeg" ) { } else if (frame_content_type != "image/jpeg") {
/* Unsupported frame type */ /* Unsupported frame type */
Error("Unsupported frame: %s",frame_content_type.c_str()); Error("Unsupported frame: %s", frame_content_type.c_str());
} else if(frame_content_length > databuffer.size()) { } else if (frame_content_length > databuffer.size()) {
/* Incomplete frame, wait for more data */ /* Incomplete frame, wait for more data */
need_more_data = true; need_more_data = true;
} else { } else {
/* All good. decode the image */ /* All good. decode the image */
if (!zm_packet->image) {
Debug(4, "Allocating image");
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
zm_packet->keyframe = 1;
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
zm_packet->packet.stream_index = mVideoStreamId;
zm_packet->stream = mVideoStream;
zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
frameComplete = true; frameComplete = true;
} }
@ -312,11 +340,19 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
need_more_data = false; need_more_data = false;
} }
} else if (mode == MODE_SINGLE) { } else if (mode == MODE_SINGLE) {
/* Check if we have anything */ /* Check if we have anything */
if (!single_offsets.empty()) { if (!single_offsets.empty()) {
if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) { if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) {
/* Extract frame */ /* Extract frame */
if (!zm_packet->image) {
Debug(4, "Allocating image");
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
zm_packet->keyframe = 1;
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
zm_packet->packet.stream_index = mVideoStreamId;
zm_packet->stream = mVideoStream;
zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
single_offsets.pop_front(); single_offsets.pop_front();
frameComplete = true; frameComplete = true;
@ -345,15 +381,15 @@ int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
/* Release the mutex */ /* Release the mutex */
unlock(); unlock();
if(!frameComplete) if (!frameComplete)
return 0; return 0;
return 1; return 1;
} }
int cURLCamera::PostCapture() { int cURLCamera::PostCapture() {
// Nothing to do here // Nothing to do here
return( 0 ); return 1;
} }
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
@ -364,7 +400,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *
/* Signal data available */ /* Signal data available */
int nRet = pthread_cond_signal(&data_available_cond); int nRet = pthread_cond_signal(&data_available_cond);
if ( nRet != 0 ) { if (nRet != 0) {
Error("Failed signaling data available condition variable: %s",strerror(nRet)); Error("Failed signaling data available condition variable: %s",strerror(nRet));
unlock(); unlock();
return -16; return -16;
@ -379,18 +415,18 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) {
std::string header; std::string header;
header.assign((const char*)buffer, size*nmemb); header.assign((const char*)buffer, size*nmemb);
Debug(4,"Got header: %s",header.c_str());
/* Check Content-Type header */ Debug(4, "Got header: %s", header.c_str());
if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
/* Check Content-Type header */
if (strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
size_t pos = header.find(';'); size_t pos = header.find(';');
if(pos != std::string::npos) { if (pos != std::string::npos) {
header.erase(pos, std::string::npos); header.erase(pos, std::string::npos);
} }
pos = header.rfind(' '); pos = header.rfind(' ');
if(pos == std::string::npos) { if (pos == std::string::npos) {
pos = header.find(':'); pos = header.find(':');
} }
@ -401,17 +437,17 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi
const char* multipart_match = "multipart/x-mixed-replace"; const char* multipart_match = "multipart/x-mixed-replace";
const char* image_jpeg_match = "image/jpeg"; const char* image_jpeg_match = "image/jpeg";
if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { if (strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
Debug(7,"Content type matched as multipart/x-mixed-replace"); Debug(7, "Content type matched as multipart/x-mixed-replace");
mode = MODE_STREAM; mode = MODE_STREAM;
} else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { } else if (strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
Debug(7,"Content type matched as image/jpeg"); Debug(7, "Content type matched as image/jpeg");
mode = MODE_SINGLE; mode = MODE_SINGLE;
} }
unlock(); unlock();
} }
/* Return bytes processed */ /* Return bytes processed */
return size*nmemb; return size*nmemb;
} }
@ -421,7 +457,7 @@ void* cURLCamera::thread_func() {
double dSize; double dSize;
c = (*curl_easy_init_f)(); c = (*curl_easy_init_f)();
if(c == nullptr) { if (c == nullptr) {
dlclose(curl_lib); dlclose(curl_lib);
Error("Failed getting easy handle from libcurl"); Error("Failed getting easy handle from libcurl");
tRet = -51; tRet = -51;
@ -431,99 +467,99 @@ void* cURLCamera::thread_func() {
CURLcode cRet; CURLcode cRet;
/* Set URL */ /* Set URL */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str());
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet));
tRet = -52; tRet = -52;
return (void*)tRet; return (void*)tRet;
} }
/* Header callback */ /* Header callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -53; tRet = -53;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -54; tRet = -54;
return (void*)tRet; return (void*)tRet;
} }
/* Data callback */ /* Data callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -55; tRet = -55;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -56; tRet = -56;
return (void*)tRet; return (void*)tRet;
} }
/* Progress callback */ /* Progress callback */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0); cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -57; tRet = -57;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
tRet = -58; tRet = -58;
return (void*)tRet; return (void*)tRet;
} }
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet));
tRet = -59; tRet = -59;
return (void*)tRet; return (void*)tRet;
} }
/* Set username and password */ /* Set username and password */
if(!mUser.empty()) { if (!mUser.empty()) {
cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str());
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet));
} }
if(!mPass.empty()) { if (!mPass.empty()) {
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str()); cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str());
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet)); Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet));
} }
/* Authenication preference */ /* Authenication preference */
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
if(cRet != CURLE_OK) if (cRet != CURLE_OK)
Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet)); Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet));
/* Work loop */ /* Work loop */
for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { for (int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
tRet = 0; tRet = 0;
while(!bTerminate) { while (!bTerminate) {
/* Do the work */ /* Do the work */
cRet = (*curl_easy_perform_f)(c); cRet = (*curl_easy_perform_f)(c);
if(mode == MODE_SINGLE) { if (mode == MODE_SINGLE) {
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
break; break;
} }
/* Attempt to get the size of the file */ /* Attempt to get the size of the file */
cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
if(cRet != CURLE_OK) { if (cRet != CURLE_OK) {
break; break;
} }
/* We need to lock for the offsets array and the condition variable */ /* We need to lock for the offsets array and the condition variable */
lock(); lock();
/* Push the size into our offsets array */ /* Push the size into our offsets array */
if(dSize > 0) { if (dSize > 0) {
single_offsets.push_back(dSize); single_offsets.push_back(dSize);
} else { } else {
Error("Unable to get the size of the image"); Error("Unable to get the size of the image");
@ -532,7 +568,7 @@ void* cURLCamera::thread_func() {
} }
/* Signal the request complete condition variable */ /* Signal the request complete condition variable */
tRet = pthread_cond_signal(&request_complete_cond); tRet = pthread_cond_signal(&request_complete_cond);
if(tRet != 0) { if (tRet != 0) {
Error("Failed signaling request completed condition variable: %s",strerror(tRet)); Error("Failed signaling request completed condition variable: %s",strerror(tRet));
tRet = -61; tRet = -61;
return (void*)tRet; return (void*)tRet;
@ -546,13 +582,13 @@ void* cURLCamera::thread_func() {
} }
/* Return value checking */ /* Return value checking */
if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { if (cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
/* Aborted */ /* Aborted */
break; break;
} else if (cRet != CURLE_OK) { } else if (cRet != CURLE_OK) {
/* Some error */ /* Some error */
Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet)); Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet));
if(attempt < CURL_MAXRETRY) { if (attempt < CURL_MAXRETRY) {
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY); Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
/* Do a reset */ /* Do a reset */
lock(); lock();
@ -565,11 +601,11 @@ void* cURLCamera::thread_func() {
tRet = -50; tRet = -50;
} }
} }
/* Cleanup */ /* Cleanup */
(*curl_easy_cleanup_f)(c); (*curl_easy_cleanup_f)(c);
c = nullptr; c = nullptr;
return (void*)tRet; return (void*)tRet;
} }
@ -597,9 +633,9 @@ int cURLCamera::unlock() {
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
/* Signal the curl thread to terminate */ /* Signal the curl thread to terminate */
if(bTerminate) if (bTerminate)
return -10; return -10;
return 0; return 0;
} }

View File

@ -102,6 +102,7 @@ bool zmDbConnect() {
if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) { if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) {
Error("Can't set isolation level: %s", mysql_error(&dbconn)); Error("Can't set isolation level: %s", mysql_error(&dbconn));
} }
mysql_set_character_set(&dbconn, "utf8");
zmDbConnected = true; zmDbConnected = true;
return zmDbConnected; return zmDbConnected;
} }
@ -117,25 +118,25 @@ void zmDbClose() {
} }
} }
MYSQL_RES * zmDbFetch(const char * query) { MYSQL_RES *zmDbFetch(const std::string &query) {
std::lock_guard<std::mutex> lck(db_mutex); std::lock_guard<std::mutex> lck(db_mutex);
if (!zmDbConnected) { if (!zmDbConnected) {
Error("Not connected."); Error("Not connected.");
return nullptr; return nullptr;
} }
if (mysql_query(&dbconn, query)) { if (mysql_query(&dbconn, query.c_str())) {
Error("Can't run query: %s", mysql_error(&dbconn)); Error("Can't run query: %s", mysql_error(&dbconn));
return nullptr; return nullptr;
} }
MYSQL_RES *result = mysql_store_result(&dbconn); MYSQL_RES *result = mysql_store_result(&dbconn);
if (!result) { if (!result) {
Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query); Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query.c_str());
} }
return result; return result;
} // end MYSQL_RES * zmDbFetch(const char * query); }
zmDbRow *zmDbFetchOne(const char *query) { zmDbRow *zmDbFetchOne(const std::string &query) {
zmDbRow *row = new zmDbRow(); zmDbRow *row = new zmDbRow();
if (row->fetch(query)) { if (row->fetch(query)) {
return row; return row;
@ -144,13 +145,13 @@ zmDbRow *zmDbFetchOne(const char *query) {
return nullptr; return nullptr;
} }
MYSQL_RES *zmDbRow::fetch(const char *query) { MYSQL_RES *zmDbRow::fetch(const std::string &query) {
result_set = zmDbFetch(query); result_set = zmDbFetch(query);
if (!result_set) return result_set; if (!result_set) return result_set;
int n_rows = mysql_num_rows(result_set); int n_rows = mysql_num_rows(result_set);
if (n_rows != 1) { if (n_rows != 1) {
Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query); Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query.c_str());
mysql_free_result(result_set); mysql_free_result(result_set);
result_set = nullptr; result_set = nullptr;
return result_set; return result_set;
@ -160,23 +161,23 @@ MYSQL_RES *zmDbRow::fetch(const char *query) {
if (!row) { if (!row) {
mysql_free_result(result_set); mysql_free_result(result_set);
result_set = nullptr; result_set = nullptr;
Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn)); Error("Error getting row from query %s. Error is %s", query.c_str(), mysql_error(&dbconn));
} else { } else {
Debug(5, "Success"); Debug(5, "Success");
} }
return result_set; return result_set;
} }
int zmDbDo(const char *query) { int zmDbDo(const std::string &query) {
std::lock_guard<std::mutex> lck(db_mutex); std::lock_guard<std::mutex> lck(db_mutex);
if (!zmDbConnected) if (!zmDbConnected)
return 0; return 0;
int rc; int rc;
while ((rc = mysql_query(&dbconn, query)) and !zm_terminate) { while ((rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) {
Logger *logger = Logger::fetch(); Logger *logger = Logger::fetch();
Logger::Level oldLevel = logger->databaseLevel(); Logger::Level oldLevel = logger->databaseLevel();
logger->databaseLevel(Logger::NOLOG); logger->databaseLevel(Logger::NOLOG);
Error("Can't run query %s: %s", query, mysql_error(&dbconn)); Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn));
logger->databaseLevel(oldLevel); logger->databaseLevel(oldLevel);
if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) { if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) {
return rc; return rc;
@ -186,36 +187,36 @@ int zmDbDo(const char *query) {
Logger::Level oldLevel = logger->databaseLevel(); Logger::Level oldLevel = logger->databaseLevel();
logger->databaseLevel(Logger::NOLOG); logger->databaseLevel(Logger::NOLOG);
Debug(1, "Success running sql query %s", query); Debug(1, "Success running sql query %s", query.c_str());
logger->databaseLevel(oldLevel); logger->databaseLevel(oldLevel);
return 1; return 1;
} }
int zmDbDoInsert(const char *query) { int zmDbDoInsert(const std::string &query) {
std::lock_guard<std::mutex> lck(db_mutex); std::lock_guard<std::mutex> lck(db_mutex);
if (!zmDbConnected) return 0; if (!zmDbConnected) return 0;
int rc; int rc;
while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) {
Error("Can't run query %s: %s", query, mysql_error(&dbconn)); Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn));
if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) )
return 0; return 0;
} }
int id = mysql_insert_id(&dbconn); int id = mysql_insert_id(&dbconn);
Debug(2, "Success running sql insert %s. Resulting id is %d", query, id); Debug(2, "Success running sql insert %s. Resulting id is %d", query.c_str(), id);
return id; return id;
} }
int zmDbDoUpdate(const char *query) { int zmDbDoUpdate(const std::string &query) {
std::lock_guard<std::mutex> lck(db_mutex); std::lock_guard<std::mutex> lck(db_mutex);
if (!zmDbConnected) return 0; if (!zmDbConnected) return 0;
int rc; int rc;
while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) {
Error("Can't run query %s: %s", query, mysql_error(&dbconn)); Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn));
if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) )
return -rc; return -rc;
} }
int affected = mysql_affected_rows(&dbconn); int affected = mysql_affected_rows(&dbconn);
Debug(2, "Success running sql update %s. Rows modified %d", query, affected); Debug(2, "Success running sql update %s. Rows modified %d", query.c_str(), affected);
return affected; return affected;
} }
@ -255,7 +256,7 @@ void zmDbQueue::process() {
// My idea for leaving the locking around each sql statement is to allow // My idea for leaving the locking around each sql statement is to allow
// other db writers to get a chance // other db writers to get a chance
lock.unlock(); lock.unlock();
zmDbDo(sql.c_str()); zmDbDo(sql);
lock.lock(); lock.lock();
} }
} }
@ -267,3 +268,15 @@ void zmDbQueue::push(std::string &&sql) {
mQueue.push(std::move(sql)); mQueue.push(std::move(sql));
mCondition.notify_all(); mCondition.notify_all();
} }
std::string zmDbEscapeString(const std::string& to_escape) {
// According to docs, size of safer_whatever must be 2 * length + 1
// due to unicode conversions + null terminator.
std::string escaped((to_escape.length() * 2) + 1, '\0');
size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped[0], to_escape.c_str(), to_escape.length());
escaped.resize(escaped_len);
return escaped;
}

View File

@ -38,7 +38,6 @@ class zmDbQueue {
public: public:
zmDbQueue(); zmDbQueue();
~zmDbQueue(); ~zmDbQueue();
void push(const char *sql) { return push(std::string(sql)); };
void push(std::string &&sql); void push(std::string &&sql);
void process(); void process();
void stop(); void stop();
@ -50,7 +49,7 @@ class zmDbRow {
MYSQL_ROW row; MYSQL_ROW row;
public: public:
zmDbRow() : result_set(nullptr), row(nullptr) { }; zmDbRow() : result_set(nullptr), row(nullptr) { };
MYSQL_RES *fetch(const char *query); MYSQL_RES *fetch(const std::string &query);
zmDbRow(MYSQL_RES *, MYSQL_ROW *row); zmDbRow(MYSQL_RES *, MYSQL_ROW *row);
~zmDbRow(); ~zmDbRow();
@ -67,15 +66,15 @@ extern zmDbQueue dbQueue;
extern bool zmDbConnected; extern bool zmDbConnected;
extern bool zmDbConnected;
bool zmDbConnect(); bool zmDbConnect();
void zmDbClose(); void zmDbClose();
int zmDbDo(const char *query); int zmDbDo(const std::string &query);
int zmDbDoInsert(const char *query); int zmDbDoInsert(const std::string &query);
int zmDbDoUpdate(const char *query); int zmDbDoUpdate(const std::string &query);
MYSQL_RES * zmDbFetch(const char *query); MYSQL_RES * zmDbFetch(const std::string &query);
zmDbRow *zmDbFetchOne(const char *query); zmDbRow *zmDbFetchOne(const std::string &query);
std::string zmDbEscapeString(const std::string& to_escape);
#endif // ZM_DB_H #endif // ZM_DB_H

View File

@ -32,8 +32,6 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
//#define USE_PREPARED_SQL 1
const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" };
#define MAX_DB_FRAMES 100 #define MAX_DB_FRAMES 100
@ -43,13 +41,13 @@ Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = {};
Event::Event( Event::Event(
Monitor *p_monitor, Monitor *p_monitor,
struct timeval p_start_time, SystemTimePoint p_start_time,
const std::string &p_cause, const std::string &p_cause,
const StringSetMap &p_noteSetMap const StringSetMap &p_noteSetMap
) : ) :
id(0), id(0),
monitor(p_monitor), monitor(p_monitor),
start_time(SystemTimePoint(zm::chrono::duration_cast<Microseconds>(p_start_time))), start_time(p_start_time),
end_time(), end_time(),
cause(p_cause), cause(p_cause),
noteSetMap(p_noteSetMap), noteSetMap(p_noteSetMap),
@ -111,7 +109,7 @@ Event::Event(
"INSERT INTO `Events` " "INSERT INTO `Events` "
"( `MonitorId`, `StorageId`, `Name`, `StartDateTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )" "( `MonitorId`, `StorageId`, `Name`, `StartDateTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )"
" VALUES " " VALUES "
"( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )", "( %d, %d, 'New Event', from_unixtime(%" PRId64 "), %u, %u, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )",
monitor->Id(), monitor->Id(),
storage->Id(), storage->Id(),
static_cast<int64>(std::chrono::system_clock::to_time_t(start_time)), static_cast<int64>(std::chrono::system_clock::to_time_t(start_time)),
@ -122,12 +120,11 @@ Event::Event(
state_id, state_id,
monitor->getOrientation(), monitor->getOrientation(),
0, 0,
"", "",
save_jpegs, save_jpegs,
storage->SchemeString().c_str() storage->SchemeString().c_str()
); );
id = zmDbDoInsert(sql);
id = zmDbDoInsert(sql.c_str());
if ( !SetPath(storage) ) { if ( !SetPath(storage) ) {
// Try another // Try another
@ -140,7 +137,7 @@ Event::Event(
Debug(1, "%s", sql.c_str()); Debug(1, "%s", sql.c_str());
storage = nullptr; storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if ( result ) { if ( result ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0])); storage = new Storage(atoi(dbrow[0]));
@ -159,7 +156,7 @@ Event::Event(
if ( monitor->ServerId() ) if ( monitor->ServerId() )
sql += stringtf(" OR ServerId != %u", monitor->ServerId()); sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql.c_str()); result = zmDbFetch(sql);
if ( result ) { if ( result ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0])); storage = new Storage(atoi(dbrow[0]));
@ -177,7 +174,7 @@ Event::Event(
Warning("Failed to find a storage area to save events."); Warning("Failed to find a storage area to save events.");
} }
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
zmDbDo(sql.c_str()); zmDbDo(sql);
} // end if ! setPath(Storage) } // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str()); Debug(1, "Using storage area at %s", path.c_str());
@ -213,14 +210,14 @@ Event::Event(
if ( ! ( save_jpegs & 1 ) ) { if ( ! ( save_jpegs & 1 ) ) {
save_jpegs |= 1; // Turn on jpeg storage save_jpegs |= 1; // Turn on jpeg storage
sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id); sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id);
zmDbDo(sql.c_str()); zmDbDo(sql);
} }
} else { } else {
sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id);
zmDbDo(sql.c_str()); zmDbDo(sql);
} }
} // end if GetOptVideoWriter } // end if GetOptVideoWriter
} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) }
Event::~Event() { Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
@ -255,7 +252,7 @@ Event::~Event() {
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
id); id);
if (!zmDbDoUpdate(sql.c_str())) { if (!zmDbDoUpdate(sql)) {
// Name might have been changed during recording, so just do the update without changing the name. // Name might have been changed during recording, so just do the update without changing the name.
sql = stringtf( sql = stringtf(
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64,
@ -264,7 +261,7 @@ Event::~Event() {
frames, alarm_frames, frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
id); id);
zmDbDoUpdate(sql.c_str()); zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording } // end if no changed rows due to Name change during recording
} // Event::~Event() } // Event::~Event()
@ -282,20 +279,14 @@ void Event::createNotes(std::string &notes) {
} }
} // void Event::createNotes(std::string &notes) } // void Event::createNotes(std::string &notes)
bool Event::WriteFrameImage( bool Event::WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame) const {
Image *image,
timeval timestamp,
const char *event_file,
bool alarm_frame) const {
int thisquality = int thisquality =
(alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ? (alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ?
config.jpeg_alarm_file_quality : 0; // quality to use, zero is default config.jpeg_alarm_file_quality : 0; // quality to use, zero is default
bool rc; bool rc;
SystemTimePoint jpeg_timestamp = SystemTimePoint jpeg_timestamp = monitor->Exif() ? timestamp : SystemTimePoint();
monitor->Exif() ? SystemTimePoint(zm::chrono::duration_cast<Microseconds>(timestamp)) : SystemTimePoint();
if (!config.timestamp_on_capture) { if (!config.timestamp_on_capture) {
// stash the image we plan to use in another pointer regardless if timestamped. // stash the image we plan to use in another pointer regardless if timestamped.
@ -309,7 +300,7 @@ bool Event::WriteFrameImage(
} }
return rc; return rc;
} // end Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) }
bool Event::WritePacket(const std::shared_ptr<ZMPacket>&packet) { bool Event::WritePacket(const std::shared_ptr<ZMPacket>&packet) {
if (videoStore->writePacket(packet) < 0) if (videoStore->writePacket(packet) < 0)
@ -357,65 +348,15 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
} // end if have old notes } // end if have old notes
} // end if have new notes } // end if have new notes
if ( update ) { if (update) {
std::string notes; std::string notes;
createNotes(notes); createNotes(notes);
Debug(2, "Updating notes for event %" PRIu64 ", '%s'", id, notes.c_str()); Debug(2, "Updating notes for event %" PRIu64 ", '%s'", id, notes.c_str());
#if USE_PREPARED_SQL
static MYSQL_STMT *stmt = 0;
char notesStr[ZM_SQL_MED_BUFSIZ] = ""; std::string sql = stringtf("UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64,
unsigned long notesLen = 0; zmDbEscapeString(notes).c_str(), id);
if ( !stmt ) {
const char *sql = "UPDATE `Events` SET `Notes` = ? WHERE `Id` = ?";
stmt = mysql_stmt_init(&dbconn);
if ( mysql_stmt_prepare(stmt, sql, strlen(sql)) ) {
Fatal("Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt));
}
/* Get the parameter count from the statement */
if ( mysql_stmt_param_count(stmt) != 2 ) {
Error("Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count(stmt), sql);
}
MYSQL_BIND bind[2];
memset(bind, 0, sizeof(bind));
/* STRING PARAM */
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = (char *)notesStr;
bind[0].buffer_length = sizeof(notesStr);
bind[0].is_null = 0;
bind[0].length = &notesLen;
bind[1].buffer_type= MYSQL_TYPE_LONG;
bind[1].buffer= (char *)&id;
bind[1].is_null= 0;
bind[1].length= 0;
/* Bind the buffers */
if ( mysql_stmt_bind_param(stmt, bind) ) {
Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt));
}
} // end if ! stmt
strncpy(notesStr, notes.c_str(), sizeof(notesStr));
if ( mysql_stmt_execute(stmt) ) {
Error("Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt));
}
#else
char sql[ZM_SQL_LGE_BUFSIZ];
static char escapedNotes[ZM_SQL_MED_BUFSIZ];
mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length());
snprintf(sql, sizeof(sql), "UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, escapedNotes, id);
dbQueue.push(std::move(sql)); dbQueue.push(std::move(sql));
#endif
} // end if update } // end if update
} // void Event::updateNotes(const StringSetMap &newNoteSetMap) } // void Event::updateNotes(const StringSetMap &newNoteSetMap)
@ -439,7 +380,7 @@ void Event::AddPacket(const std::shared_ptr<ZMPacket>&packet) {
if ((packet->codec_type == AVMEDIA_TYPE_VIDEO) or packet->image) { if ((packet->codec_type == AVMEDIA_TYPE_VIDEO) or packet->image) {
AddFrame(packet->image, packet->timestamp, packet->zone_stats, packet->score, packet->analysis_image); AddFrame(packet->image, packet->timestamp, packet->zone_stats, packet->score, packet->analysis_image);
} }
end_time = SystemTimePoint(zm::chrono::duration_cast<Microseconds>(packet->timestamp)); end_time = packet->timestamp;
} }
void Event::WriteDbFrames() { void Event::WriteDbFrames() {
@ -456,7 +397,7 @@ void Event::WriteDbFrames() {
frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %.2f, %d ),", frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %.2f, %d ),",
id, frame->frame_id, id, frame->frame_id,
frame_type_names[frame->type], frame_type_names[frame->type],
frame->timestamp.tv_sec, std::chrono::system_clock::to_time_t(frame->timestamp),
std::chrono::duration_cast<FPSeconds>(frame->delta).count(), std::chrono::duration_cast<FPSeconds>(frame->delta).count(),
frame->score); frame->score);
if (config.record_event_stats and frame->zone_stats.size()) { if (config.record_event_stats and frame->zone_stats.size()) {
@ -483,7 +424,7 @@ void Event::WriteDbFrames() {
} // end while frames } // end while frames
// The -1 is for the extra , added for values above // The -1 is for the extra , added for values above
frame_insert_sql.erase(frame_insert_sql.size()-1); frame_insert_sql.erase(frame_insert_sql.size()-1);
//zmDbDo(frame_insert_sql.c_str()); //zmDbDo(frame_insert_sql);
dbQueue.push(std::move(frame_insert_sql)); dbQueue.push(std::move(frame_insert_sql));
if (stats_insert_sql.size() > 208) { if (stats_insert_sql.size() > 208) {
// The -1 is for the extra , added for values above // The -1 is for the extra , added for values above
@ -493,13 +434,12 @@ void Event::WriteDbFrames() {
} }
} // end void Event::WriteDbFrames() } // end void Event::WriteDbFrames()
void Event::AddFrame( void Event::AddFrame(Image *image,
Image *image, SystemTimePoint timestamp,
struct timeval timestamp, const std::vector<ZoneStats> &zone_stats,
const std::vector<ZoneStats> &zone_stats, int score,
int score, Image *alarm_image) {
Image *alarm_image) { if (timestamp.time_since_epoch() == Seconds(0)) {
if (!timestamp.tv_sec) {
Warning("Not adding new frame, zero timestamp"); Warning("Not adding new frame, zero timestamp");
return; return;
} }
@ -525,7 +465,7 @@ void Event::AddFrame(
if (image) { if (image) {
if (save_jpegs & 1) { if (save_jpegs & 1) {
std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); std::string event_file = stringtf(staticConfig.capture_file_format.c_str(), path.c_str(), frames);
Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str()); Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str());
if (!WriteFrameImage(image, timestamp, event_file.c_str())) { if (!WriteFrameImage(image, timestamp, event_file.c_str())) {
Error("Failed to write frame image"); Error("Failed to write frame image");
@ -555,7 +495,7 @@ void Event::AddFrame(
} }
if (alarm_image and (save_jpegs & 2)) { if (alarm_image and (save_jpegs & 2)) {
std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames); std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames);
Debug(1, "Writing analysis frame %d", frames); Debug(1, "Writing analysis frame %d", frames);
if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) {
Error("Failed to write analysis frame image"); Error("Failed to write analysis frame image");
@ -576,12 +516,10 @@ void Event::AddFrame(
or ( monitor_state == Monitor::ALARM ) or ( monitor_state == Monitor::ALARM )
or ( monitor_state == Monitor::PREALARM ); or ( monitor_state == Monitor::PREALARM );
SystemTimePoint timestamp_us = SystemTimePoint(zm::chrono::duration_cast<Microseconds>(timestamp));
if (db_frame) { if (db_frame) {
Microseconds delta_time = std::chrono::duration_cast<Microseconds>(timestamp_us - start_time); Microseconds delta_time = std::chrono::duration_cast<Microseconds>(timestamp - start_time);
Debug(1, "Frame delta is %.2f s - %.2f s = %.2f s, score %u zone_stats.size %zu", Debug(1, "Frame delta is %.2f s - %.2f s = %.2f s, score %u zone_stats.size %zu",
FPSeconds(timestamp_us.time_since_epoch()).count(), FPSeconds(timestamp.time_since_epoch()).count(),
FPSeconds(start_time.time_since_epoch()).count(), FPSeconds(start_time.time_since_epoch()).count(),
FPSeconds(delta_time).count(), FPSeconds(delta_time).count(),
score, score,
@ -621,8 +559,8 @@ void Event::AddFrame(
if (score > (int) max_score) { if (score > (int) max_score) {
max_score = score; max_score = score;
} }
end_time = timestamp_us; end_time = timestamp;
} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) }
bool Event::SetPath(Storage *storage) { bool Event::SetPath(Storage *storage) {
scheme = storage->Scheme(); scheme = storage->Scheme();

View File

@ -24,6 +24,7 @@
#include "zm_define.h" #include "zm_define.h"
#include "zm_storage.h" #include "zm_storage.h"
#include "zm_time.h" #include "zm_time.h"
#include "zm_utils.h"
#include "zm_zone.h" #include "zm_zone.h"
#include <map> #include <map>
@ -96,12 +97,10 @@ class Event {
static bool OpenFrameSocket(int); static bool OpenFrameSocket(int);
static bool ValidateFrameSocket(int); static bool ValidateFrameSocket(int);
Event( Event(Monitor *p_monitor,
Monitor *p_monitor, SystemTimePoint p_start_time,
struct timeval p_start_time, const std::string &p_cause,
const std::string &p_cause, const StringSetMap &p_noteSetMap);
const StringSetMap &p_noteSetMap
);
~Event(); ~Event();
uint64_t Id() const { return id; } uint64_t Id() const { return id; }
@ -109,46 +108,38 @@ class Event {
int Frames() const { return frames; } int Frames() const { return frames; }
int AlarmFrames() const { return alarm_frames; } int AlarmFrames() const { return alarm_frames; }
timeval StartTime() const { return zm::chrono::duration_cast<timeval>(start_time.time_since_epoch()); } SystemTimePoint StartTime() const { return start_time; }
timeval EndTime() const { return zm::chrono::duration_cast<timeval>(end_time.time_since_epoch()); } SystemTimePoint EndTime() const { return end_time; }
void AddPacket(const std::shared_ptr<ZMPacket> &p); void AddPacket(const std::shared_ptr<ZMPacket> &p);
bool WritePacket(const std::shared_ptr<ZMPacket> &p); bool WritePacket(const std::shared_ptr<ZMPacket> &p);
bool SendFrameImage(const Image *image, bool alarm_frame=false); bool SendFrameImage(const Image *image, bool alarm_frame=false);
bool WriteFrameImage( bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const;
Image *image,
struct timeval timestamp,
const char *event_file,
bool alarm_frame=false
) const;
void updateNotes(const StringSetMap &stringSetMap); void updateNotes(const StringSetMap &stringSetMap);
void AddFrame( void AddFrame(Image *image,
Image *image, SystemTimePoint timestamp,
struct timeval timestamp, const std::vector<ZoneStats> &stats,
const std::vector<ZoneStats> &stats, int score = 0,
int score=0, Image *alarm_image = nullptr);
Image *alarm_image=nullptr
);
private: private:
void WriteDbFrames(); void WriteDbFrames();
bool SetPath(Storage *storage); bool SetPath(Storage *storage);
public: public:
static const char *getSubPath(tm time) { static std::string getSubPath(tm time) {
static char subpath[PATH_MAX] = ""; std::string subpath = stringtf("%02d/%02d/%02d/%02d/%02d/%02d",
snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time.tm_year - 100, time.tm_mon + 1, time.tm_mday,
time.tm_year-100, time.tm_mon+1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec);
time.tm_hour, time.tm_min, time.tm_sec); return subpath;
return subpath; }
} static std::string getSubPath(time_t *time) {
static const char *getSubPath(time_t *time) { tm time_tm = {};
tm time_tm = {}; localtime_r(time, &time_tm);
localtime_r(time, &time_tm); return Event::getSubPath(time_tm);
return Event::getSubPath(time_tm); }
}
const char* getEventFile() const { const char* getEventFile() const {
return video_file.c_str(); return video_file.c_str();
@ -174,7 +165,7 @@ class Event {
} }
static void AddPreAlarmFrame( static void AddPreAlarmFrame(
Image *image, Image *image,
struct timeval timestamp, SystemTimePoint timestamp,
int score=0, int score=0,
Image *alarm_frame=nullptr Image *alarm_frame=nullptr
) { ) {

View File

@ -44,9 +44,9 @@ constexpr Milliseconds EventStream::STREAM_PAUSE_WAIT;
bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_time) { bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_time) {
std::string sql = stringtf("SELECT `Id` FROM `Events` WHERE " std::string sql = stringtf("SELECT `Id` FROM `Events` WHERE "
"`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld " "`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld "
"ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); "ORDER BY `Id` ASC LIMIT 1", monitor_id, std::chrono::system_clock::to_time_t(event_time));
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) if (!result)
exit(-1); exit(-1);
@ -117,7 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
"(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS FramesDuration, " "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS FramesDuration, "
"`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id);
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) { if (!result) {
exit(-1); exit(-1);
} }
@ -145,7 +145,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->duration = std::chrono::duration_cast<Microseconds>(event_data->end_time - event_data->start_time); event_data->duration = std::chrono::duration_cast<Microseconds>(event_data->end_time - event_data->start_time);
event_data->frames_duration = event_data->frames_duration =
std::chrono::duration_cast<Microseconds>(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0)); std::chrono::duration_cast<Microseconds>(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0));
strncpy(event_data->video_file, dbrow[6], sizeof(event_data->video_file) - 1); event_data->video_file = std::string(dbrow[6]);
std::string scheme_str = std::string(dbrow[7]); std::string scheme_str = std::string(dbrow[7]);
if ( scheme_str == "Deep" ) { if ( scheme_str == "Deep" ) {
event_data->scheme = Storage::DEEP; event_data->scheme = Storage::DEEP;
@ -180,44 +180,41 @@ bool EventStream::loadEventData(uint64_t event_id) {
time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time);
localtime_r(&start_time_t, &event_time); localtime_r(&start_time_t, &event_time);
if ( storage_path[0] == '/' ) if (storage_path[0] == '/') {
snprintf(event_data->path, sizeof(event_data->path), event_data->path = stringtf("%s/%u/%02d/%02d/%02d/%02d/%02d/%02d",
"%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id,
storage_path, event_data->monitor_id, event_time.tm_year - 100, event_time.tm_mon + 1, event_time.tm_mday,
event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, event_time.tm_hour, event_time.tm_min, event_time.tm_sec);
event_time.tm_hour, event_time.tm_min, event_time.tm_sec); } else {
else event_data->path = stringtf("%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d",
snprintf(event_data->path, sizeof(event_data->path), staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
"%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", event_time.tm_year - 100, event_time.tm_mon + 1, event_time.tm_mday,
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_time.tm_hour, event_time.tm_min, event_time.tm_sec);
event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, }
event_time.tm_hour, event_time.tm_min, event_time.tm_sec); } else if (event_data->scheme == Storage::MEDIUM) {
} else if ( event_data->scheme == Storage::MEDIUM ) {
tm event_time = {}; tm event_time = {};
time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time);
localtime_r(&start_time_t, &event_time); localtime_r(&start_time_t, &event_time);
if ( storage_path[0] == '/' ) if (storage_path[0] == '/') {
snprintf(event_data->path, sizeof(event_data->path), event_data->path = stringtf("%s/%u/%04d-%02d-%02d/%" PRIu64,
"%s/%u/%04d-%02d-%02d/%" PRIu64, storage_path, event_data->monitor_id,
storage_path, event_data->monitor_id, event_time.tm_year + 1900, event_time.tm_mon + 1, event_time.tm_mday,
event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, event_data->event_id);
event_data->event_id); } else {
else event_data->path = stringtf("%s/%s/%u/%04d-%02d-%02d/%" PRIu64,
snprintf(event_data->path, sizeof(event_data->path), staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
"%s/%s/%u/%04d-%02d-%02d/%" PRIu64, event_time.tm_year + 1900, event_time.tm_mon + 1, event_time.tm_mday,
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id);
event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, }
event_data->event_id);
} else { } else {
if ( storage_path[0] == '/' ) if (storage_path[0] == '/') {
snprintf(event_data->path, sizeof(event_data->path), "%s/%u/%" PRIu64, event_data->path = stringtf("%s/%u/%" PRIu64, storage_path, event_data->monitor_id, event_data->event_id);
storage_path, event_data->monitor_id, event_data->event_id); } else {
else event_data->path = stringtf("%s/%s/%u/%" PRIu64,
snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%u/%" PRIu64, staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id);
event_data->event_id); }
} }
double fps = 1.0; double fps = 1.0;
@ -229,7 +226,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` "
"FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id);
result = zmDbFetch(sql.c_str()); result = zmDbFetch(sql);
if (!result) { if (!result) {
exit(-1); exit(-1);
} }
@ -289,17 +286,17 @@ bool EventStream::loadEventData(uint64_t event_id) {
} }
mysql_free_result(result); mysql_free_result(result);
if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { if (!event_data->video_file.empty() || (monitor->GetOptVideoWriter() > 0)) {
if ( !event_data->video_file[0] ) { if (event_data->video_file.empty()) {
snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); event_data->video_file = stringtf("%" PRIu64 "-%s", event_data->event_id, "video.mp4");
} }
std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file);
std::string filepath = event_data->path + "/" + event_data->video_file;
Debug(1, "Loading video file from %s", filepath.c_str()); Debug(1, "Loading video file from %s", filepath.c_str());
if ( ffmpeg_input ) delete ffmpeg_input;
delete ffmpeg_input;
ffmpeg_input = new FFmpeg_Input(); ffmpeg_input = new FFmpeg_Input();
if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) { if (ffmpeg_input->Open(filepath.c_str()) < 0) {
Warning("Unable to open ffmpeg_input %s", filepath.c_str()); Warning("Unable to open ffmpeg_input %s", filepath.c_str());
delete ffmpeg_input; delete ffmpeg_input;
ffmpeg_input = nullptr; ffmpeg_input = nullptr;
@ -640,7 +637,7 @@ bool EventStream::checkEventLoaded() {
if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) { if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) {
Debug(1, "Checking for next event %s", sql.c_str()); Debug(1, "Checking for next event %s", sql.c_str());
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) { if (!result) {
exit(-1); exit(-1);
} }
@ -690,32 +687,30 @@ bool EventStream::checkEventLoaded() {
} // void EventStream::checkEventLoaded() } // void EventStream::checkEventLoaded()
Image * EventStream::getImage( ) { Image * EventStream::getImage( ) {
static char filepath[PATH_MAX]; std::string path = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", path.c_str(), event_data->path.c_str(), curr_frame_id);
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); Image *image = new Image(path.c_str());
Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", filepath, event_data->path, curr_frame_id);
Image *image = new Image(filepath);
return image; return image;
} }
bool EventStream::sendFrame(Microseconds delta_us) { bool EventStream::sendFrame(Microseconds delta_us) {
Debug(2, "Sending frame %ld", curr_frame_id); Debug(2, "Sending frame %ld", curr_frame_id);
static char filepath[PATH_MAX]; std::string filepath;
static struct stat filestat; struct stat filestat = {};
// This needs to be abstracted. If we are saving jpgs, then load the capture file. // This needs to be abstracted. If we are saving jpgs, then load the capture file.
// If we are only saving analysis frames, then send that. // If we are only saving analysis frames, then send that.
if ( event_data->SaveJPEGs & 1 ) { if (event_data->SaveJPEGs & 1) {
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
} else if ( event_data->SaveJPEGs & 2 ) { } else if (event_data->SaveJPEGs & 2) {
snprintf(filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id); filepath = stringtf(staticConfig.analyse_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
if ( stat(filepath, &filestat) < 0 ) { if (stat(filepath.c_str(), &filestat) < 0) {
Debug(1, "analyze file %s not found will try to stream from other", filepath); Debug(1, "analyze file %s not found will try to stream from other", filepath.c_str());
snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
if ( stat(filepath, &filestat) < 0 ) { if (stat(filepath.c_str(), &filestat) < 0) {
Debug(1, "capture file %s not found either", filepath); Debug(1, "capture file %s not found either", filepath.c_str());
filepath[0] = 0; filepath = "";
} }
} }
} else if ( !ffmpeg_input ) { } else if ( !ffmpeg_input ) {
@ -724,7 +719,7 @@ bool EventStream::sendFrame(Microseconds delta_us) {
} }
if ( type == STREAM_MPEG ) { if ( type == STREAM_MPEG ) {
Image image(filepath); Image image(filepath.c_str());
Image *send_image = prepareImage(&image); Image *send_image = prepareImage(&image);
@ -739,20 +734,20 @@ bool EventStream::sendFrame(Microseconds delta_us) {
config.mpeg_timed_frames, config.mpeg_timed_frames,
delta_us.count() * 1000); delta_us.count() * 1000);
} else { } else {
bool send_raw = (type == STREAM_JPEG) && ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)) && filepath[0]; bool send_raw = (type == STREAM_JPEG) && ((scale >= ZM_SCALE_BASE) && (zoom == ZM_SCALE_BASE)) && !filepath.empty();
fprintf(stdout, "--" BOUNDARY "\r\n"); fprintf(stdout, "--" BOUNDARY "\r\n");
if ( send_raw ) { if (send_raw) {
if ( !send_file(filepath) ) { if (!send_file(filepath)) {
Error("Can't send %s: %s", filepath, strerror(errno)); Error("Can't send %s: %s", filepath.c_str(), strerror(errno));
return false; return false;
} }
} else { } else {
Image *image = nullptr; Image *image = nullptr;
if ( filepath[0] ) { if (!filepath.empty()) {
image = new Image(filepath); image = new Image(filepath.c_str());
} else if ( ffmpeg_input ) { } else if ( ffmpeg_input ) {
// Get the frame from the mp4 input // Get the frame from the mp4 input
FrameData *frame_data = &event_data->frames[curr_frame_id-1]; FrameData *frame_data = &event_data->frames[curr_frame_id-1];
@ -1088,24 +1083,24 @@ void EventStream::runStream() {
closeComms(); closeComms();
} // end void EventStream::runStream() } // end void EventStream::runStream()
bool EventStream::send_file(const char *filepath) { bool EventStream::send_file(const std::string &filepath) {
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
int img_buffer_size = 0; int img_buffer_size = 0;
uint8_t *img_buffer = temp_img_buffer; uint8_t *img_buffer = temp_img_buffer;
FILE *fdj = nullptr; FILE *fdj = nullptr;
fdj = fopen(filepath, "rb"); fdj = fopen(filepath.c_str(), "rb");
if ( !fdj ) { if ( !fdj ) {
Error("Can't open %s: %s", filepath, strerror(errno)); Error("Can't open %s: %s", filepath.c_str(), strerror(errno));
std::string error_message = stringtf("Can't open %s: %s", filepath, strerror(errno)); std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno));
return sendTextFrame(error_message.c_str()); return sendTextFrame(error_message.c_str());
} }
#if HAVE_SENDFILE #if HAVE_SENDFILE
static struct stat filestat; static struct stat filestat;
if ( fstat(fileno(fdj), &filestat) < 0 ) { if ( fstat(fileno(fdj), &filestat) < 0 ) {
fclose(fdj); /* Close the file handle */ fclose(fdj); /* Close the file handle */
Error("Failed getting information about file %s: %s", filepath, strerror(errno)); Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno));
return false; return false;
} }
if ( !filestat.st_size ) { if ( !filestat.st_size ) {
@ -1134,7 +1129,7 @@ bool EventStream::send_file(const char *filepath) {
} }
return send_buffer(img_buffer, img_buffer_size); return send_buffer(img_buffer, img_buffer_size);
} // end bool EventStream::send_file(const char * filepath) }
bool EventStream::send_buffer(uint8_t* buffer, int size) { bool EventStream::send_buffer(uint8_t* buffer, int size) {
if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) {

View File

@ -56,10 +56,10 @@ class EventStream : public StreamBase {
SystemTimePoint end_time; SystemTimePoint end_time;
Microseconds duration; Microseconds duration;
Microseconds frames_duration; Microseconds frames_duration;
char path[PATH_MAX]; std::string path;
int n_frames; // # of frame rows returned from database int n_frames; // # of frame rows returned from database
FrameData *frames; FrameData *frames;
char video_file[PATH_MAX]; std::string video_file;
Storage::Schemes scheme; Storage::Schemes scheme;
int SaveJPEGs; int SaveJPEGs;
Monitor::Orientation Orientation; Monitor::Orientation Orientation;
@ -124,7 +124,7 @@ class EventStream : public StreamBase {
void runStream() override; void runStream() override;
Image *getImage(); Image *getImage();
private: private:
bool send_file(const char *filepath); bool send_file(const std::string &filepath);
bool send_buffer(uint8_t * buffer, int size); bool send_buffer(uint8_t * buffer, int size);
Storage *storage; Storage *storage;
FFmpeg_Input *ffmpeg_input; FFmpeg_Input *ffmpeg_input;

View File

@ -213,7 +213,7 @@ void zm_dump_codecpar(const AVCodecParameters *par) {
void zm_dump_codec(const AVCodecContext *codec) { void zm_dump_codec(const AVCodecContext *codec) {
Debug(1, "Dumping codec_context codec_type %d %s codec_id %d %s width %d height %d timebase %d/%d format %s profile %d level %d " Debug(1, "Dumping codec_context codec_type %d %s codec_id %d %s width %d height %d timebase %d/%d format %s profile %d level %d "
"gop_size %d has_b_frames %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d bit_rate %ld extradata:%d:%s", "gop_size %d has_b_frames %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d bit_rate %" PRId64 " extradata:%d:%s",
codec->codec_type, codec->codec_type,
av_get_media_type_string(codec->codec_type), av_get_media_type_string(codec->codec_type),
codec->codec_id, codec->codec_id,

View File

@ -27,16 +27,10 @@
extern "C" { extern "C" {
#include <libavutil/time.h> #include <libavutil/time.h>
#if HAVE_LIBAVUTIL_HWCONTEXT_H
#include <libavutil/hwcontext.h>
#endif
#include <libavutil/pixdesc.h>
} }
#include <string> TimePoint start_read_time;
time_t start_read_time;
#if HAVE_LIBAVUTIL_HWCONTEXT_H #if HAVE_LIBAVUTIL_HWCONTEXT_H
#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0)
static enum AVPixelFormat hw_pix_fmt; static enum AVPixelFormat hw_pix_fmt;
@ -61,31 +55,26 @@ static enum AVPixelFormat get_hw_format(
} }
#if !LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) #if !LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0)
static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) { static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) {
enum AVPixelFormat fmt;
switch (type) { switch (type) {
case AV_HWDEVICE_TYPE_VAAPI: case AV_HWDEVICE_TYPE_VAAPI:
fmt = AV_PIX_FMT_VAAPI; return AV_PIX_FMT_VAAPI;
break;
case AV_HWDEVICE_TYPE_DXVA2: case AV_HWDEVICE_TYPE_DXVA2:
fmt = AV_PIX_FMT_DXVA2_VLD; return AV_PIX_FMT_DXVA2_VLD;
break;
case AV_HWDEVICE_TYPE_D3D11VA: case AV_HWDEVICE_TYPE_D3D11VA:
fmt = AV_PIX_FMT_D3D11; return AV_PIX_FMT_D3D11;
break;
case AV_HWDEVICE_TYPE_VDPAU: case AV_HWDEVICE_TYPE_VDPAU:
fmt = AV_PIX_FMT_VDPAU; return AV_PIX_FMT_VDPAU;
break;
case AV_HWDEVICE_TYPE_CUDA: case AV_HWDEVICE_TYPE_CUDA:
fmt = AV_PIX_FMT_CUDA; return AV_PIX_FMT_CUDA;
break; #ifdef AV_HWDEVICE_TYPE_MMAL
case AV_HWDEVICE_TYPE_MMAL:
return AV_PIX_FMT_MMAL;
#endif
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
fmt = AV_PIX_FMT_VIDEOTOOLBOX; return AV_PIX_FMT_VIDEOTOOLBOX;
break;
default: default:
fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE;
break;
} }
return fmt;
} }
#endif #endif
#endif #endif
@ -169,7 +158,7 @@ FfmpegCamera::~FfmpegCamera() {
} }
int FfmpegCamera::PrimeCapture() { int FfmpegCamera::PrimeCapture() {
start_read_time = time(nullptr); start_read_time = std::chrono::steady_clock::now();
if ( mCanCapture ) { if ( mCanCapture ) {
Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Debug(1, "Priming capture from %s, Closing", mPath.c_str());
Close(); Close();
@ -188,7 +177,7 @@ int FfmpegCamera::PreCapture() {
int FfmpegCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) { int FfmpegCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
if (!mCanCapture) return -1; if (!mCanCapture) return -1;
start_read_time = time(nullptr); start_read_time = std::chrono::steady_clock::now();
int ret; int ret;
AVFormatContext *formatContextPtr; AVFormatContext *formatContextPtr;
@ -237,8 +226,8 @@ int FfmpegCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
zm_packet->set_packet(&packet); zm_packet->set_packet(&packet);
zm_packet->stream = stream; zm_packet->stream = stream;
zm_packet->pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); zm_packet->pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q);
if ( packet.pts != AV_NOPTS_VALUE ) { if (packet.pts != AV_NOPTS_VALUE) {
if ( stream == mVideoStream ) { if (stream == mVideoStream) {
if (mFirstVideoPTS == AV_NOPTS_VALUE) if (mFirstVideoPTS == AV_NOPTS_VALUE)
mFirstVideoPTS = packet.pts; mFirstVideoPTS = packet.pts;
@ -558,11 +547,12 @@ int FfmpegCamera::FfmpegInterruptCallback(void *ctx) {
Debug(1, "Received terminate in cb"); Debug(1, "Received terminate in cb");
return zm_terminate; return zm_terminate;
} }
time_t now = time(nullptr);
if (now - start_read_time > 10) { TimePoint now = std::chrono::steady_clock::now();
Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10", if (now - start_read_time > Seconds(10)) {
static_cast<int64>(now), Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10 s",
static_cast<int64>(start_read_time)); static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(start_read_time.time_since_epoch()).count()));
return 1; return 1;
} }
return 0; return 0;

View File

@ -269,4 +269,4 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
} }
return get_frame(stream_id); return get_frame(stream_id);
} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at) }

View File

@ -29,32 +29,26 @@
#define RAW_BUFFER 512 #define RAW_BUFFER 512
#define PIPE_SIZE 1024*1024 #define PIPE_SIZE 1024*1024
void Fifo::file_create_if_missing( void Fifo::file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo) {
const char * path, struct stat st = {};
bool is_fifo,
bool delete_fake_fifo if (stat(path.c_str(), &st) == 0) {
) { if ((!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo)
static struct stat st;
if ( stat(path, &st) == 0 ) {
if ( (!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo )
return; return;
Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path); Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path.c_str());
unlink(path); unlink(path.c_str());
} }
if (!is_fifo) { if (!is_fifo) {
Debug(5, "Creating non fifo file as requested: %s", path); Debug(5, "Creating non fifo file as requested: %s", path.c_str());
int fd = ::open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); int fd = ::open(path.c_str(), O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
::close(fd); ::close(fd);
return; return;
} }
Debug(5, "Making fifo file of: %s", path); Debug(5, "Making fifo file of: %s", path.c_str());
mkfifo(path, S_IRUSR|S_IWUSR); mkfifo(path.c_str(), S_IRUSR | S_IWUSR);
} }
void Fifo::fifo_create_if_missing( void Fifo::fifo_create_if_missing(const std::string &path, bool delete_fake_fifo) {
const char * path,
bool delete_fake_fifo
) {
file_create_if_missing(path, true, delete_fake_fifo); file_create_if_missing(path, true, delete_fake_fifo);
} }
@ -62,7 +56,8 @@ Fifo::~Fifo() {
close(); close();
} }
bool Fifo::open() { bool Fifo::open() {
fifo_create_if_missing(path.c_str()); fifo_create_if_missing(path);
if (!on_blocking_abort) { if (!on_blocking_abort) {
if ( (outfile = fopen(path.c_str(), "wb")) == nullptr ) { if ( (outfile = fopen(path.c_str(), "wb")) == nullptr ) {
Error("Can't open %s for writing: %s", path.c_str(), strerror(errno)); Error("Can't open %s for writing: %s", path.c_str(), strerror(errno));

View File

@ -32,11 +32,8 @@ class Fifo {
int raw_fd; int raw_fd;
public: public:
static void file_create_if_missing( static void file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo = true);
const char * path, static void fifo_create_if_missing(const std::string &path, bool delete_fake_fifo = true);
bool is_fifo,
bool delete_fake_fifo = true
);
Fifo() : Fifo() :
on_blocking_abort(true), on_blocking_abort(true),
@ -51,12 +48,6 @@ class Fifo {
{} {}
~Fifo(); ~Fifo();
static void fifo_create_if_missing(
const char * path,
bool delete_fake_fifo = true);
static bool writePacket(std::string filename, const ZMPacket &packet); static bool writePacket(std::string filename, const ZMPacket &packet);
static bool write(std::string filename, uint8_t *data, size_t size); static bool write(std::string filename, uint8_t *data, size_t size);

View File

@ -28,7 +28,7 @@
#define RAW_BUFFER 512 #define RAW_BUFFER 512
static bool zm_fifodbg_inited = false; static bool zm_fifodbg_inited = false;
FILE *zm_fifodbg_log_fd = nullptr; FILE *zm_fifodbg_log_fd = nullptr;
char zm_fifodbg_log[PATH_MAX] = ""; std::string zm_fifodbg_log;
static bool zmFifoDbgOpen() { static bool zmFifoDbgOpen() {
if ( zm_fifodbg_log_fd ) if ( zm_fifodbg_log_fd )
@ -36,7 +36,7 @@ static bool zmFifoDbgOpen() {
zm_fifodbg_log_fd = nullptr; zm_fifodbg_log_fd = nullptr;
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
Fifo::fifo_create_if_missing(zm_fifodbg_log); Fifo::fifo_create_if_missing(zm_fifodbg_log);
int fd = open(zm_fifodbg_log, O_WRONLY|O_NONBLOCK|O_TRUNC); int fd = open(zm_fifodbg_log.c_str(), O_WRONLY | O_NONBLOCK | O_TRUNC);
if ( fd < 0 ) if ( fd < 0 )
return false; return false;
int res = flock(fd, LOCK_EX | LOCK_NB); int res = flock(fd, LOCK_EX | LOCK_NB);
@ -54,8 +54,7 @@ static bool zmFifoDbgOpen() {
int zmFifoDbgInit(Monitor *monitor) { int zmFifoDbgInit(Monitor *monitor) {
zm_fifodbg_inited = true; zm_fifodbg_inited = true;
snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/dbgpipe-%u.log", zm_fifodbg_log = stringtf("%s/dbgpipe-%u.log", staticConfig.PATH_SOCKS.c_str(), monitor->Id());
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
zmFifoDbgOpen(); zmFifoDbgOpen();
return 1; return 1;
} }

View File

@ -29,9 +29,9 @@
#define RAW_BUFFER 512 #define RAW_BUFFER 512
bool FifoStream::sendRAWFrames() { bool FifoStream::sendRAWFrames() {
static unsigned char buffer[RAW_BUFFER]; static unsigned char buffer[RAW_BUFFER];
int fd = open(stream_path, O_RDONLY); int fd = open(stream_path.c_str(), O_RDONLY);
if ( fd < 0 ) { if ( fd < 0 ) {
Error("Can't open %s: %s", stream_path, strerror(errno)); Error("Can't open %s: %s", stream_path.c_str(), strerror(errno));
return false; return false;
} }
while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) { while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) {
@ -56,9 +56,9 @@ bool FifoStream::sendRAWFrames() {
bool FifoStream::sendMJEGFrames() { bool FifoStream::sendMJEGFrames() {
static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; static unsigned char buffer[ZM_MAX_IMAGE_SIZE];
int fd = open(stream_path, O_RDONLY); int fd = open(stream_path.c_str(), O_RDONLY);
if ( fd < 0 ) { if ( fd < 0 ) {
Error("Can't open %s: %s", stream_path, strerror(errno)); Error("Can't open %s: %s", stream_path.c_str(), strerror(errno));
return false; return false;
} }
total_read = 0; total_read = 0;
@ -97,28 +97,26 @@ bool FifoStream::sendMJEGFrames() {
return true; return true;
} }
void FifoStream::setStreamStart(const char * path) { void FifoStream::setStreamStart(const std::string &path) {
stream_path = strdup(path); stream_path = path;
} }
void FifoStream::setStreamStart(int monitor_id, const char * format) { void FifoStream::setStreamStart(int monitor_id, const char *format) {
char diag_path[PATH_MAX]; std::string diag_path;
std::shared_ptr<Monitor> monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); std::shared_ptr<Monitor> monitor = Monitor::Load(monitor_id, false, Monitor::QUERY);
if ( !strcmp(format, "reference") ) { if (!strcmp(format, "reference")) {
snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-r-%u.jpg", diag_path = stringtf("%s/diagpipe-r-%u.jpg", staticConfig.PATH_SOCKS.c_str(), monitor->Id());
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = MJPEG; stream_type = MJPEG;
} else if ( !strcmp(format, "delta") ) { } else if (!strcmp(format, "delta")) {
snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-d-%u.jpg", diag_path = stringtf("%s/diagpipe-d-%u.jpg", staticConfig.PATH_SOCKS.c_str(), monitor->Id());
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = MJPEG; stream_type = MJPEG;
} else { } else {
if ( strcmp(format, "raw") ) { if (strcmp(format, "raw")) {
Warning("Unknown or unspecified format. Defaulting to raw"); Warning("Unknown or unspecified format. Defaulting to raw");
} }
snprintf(diag_path, sizeof(diag_path), "%s/dbgpipe-%u.log",
staticConfig.PATH_SOCKS.c_str(), monitor->Id()); diag_path = stringtf("%s/dbgpipe-%u.log", staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = RAW; stream_type = RAW;
} }
@ -126,46 +124,48 @@ void FifoStream::setStreamStart(int monitor_id, const char * format) {
} }
void FifoStream::runStream() { void FifoStream::runStream() {
if ( stream_type == MJPEG ) { if (stream_type == MJPEG) {
fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n"); fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n");
} else { } else {
fprintf(stdout, "Content-Type: text/html\r\n\r\n"); fprintf(stdout, "Content-Type: text/html\r\n\r\n");
} }
/* only 1 person can read from a fifo at a time, so use a lock */ /* only 1 person can read from a fifo at a time, so use a lock */
char lock_file[PATH_MAX]; std::string lock_file = stringtf("%s.rlock", stream_path.c_str());
snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path);
Fifo::file_create_if_missing(lock_file, false); Fifo::file_create_if_missing(lock_file, false);
Debug(1, "Locking %s", lock_file); Debug(1, "Locking %s", lock_file.c_str());
int fd_lock = open(lock_file, O_RDONLY); int fd_lock = open(lock_file.c_str(), O_RDONLY);
if ( fd_lock < 0 ) { if (fd_lock < 0) {
Error("Can't open %s: %s", lock_file, strerror(errno)); Error("Can't open %s: %s", lock_file.c_str(), strerror(errno));
return; return;
} }
int res = flock(fd_lock, LOCK_EX | LOCK_NB); int res = flock(fd_lock, LOCK_EX | LOCK_NB);
while ( (res < 0 and errno == EAGAIN) and (! zm_terminate) ) { while ((res < 0 and errno == EAGAIN) and (!zm_terminate)) {
Warning("Flocking problem on %s: - %s", lock_file, strerror(errno)); Warning("Flocking problem on %s: - %s", lock_file.c_str(), strerror(errno));
sleep(1); sleep(1);
res = flock(fd_lock, LOCK_EX | LOCK_NB); res = flock(fd_lock, LOCK_EX | LOCK_NB);
} }
if ( res < 0 ) {
Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file, strerror(errno)); if (res < 0) {
Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file.c_str(), strerror(errno));
close(fd_lock); close(fd_lock);
return; return;
} }
while ( !zm_terminate ) { while (!zm_terminate) {
now = std::chrono::system_clock::now(); now = std::chrono::system_clock::now();
checkCommandQueue(); checkCommandQueue();
if ( stream_type == MJPEG ) { if (stream_type == MJPEG) {
if ( !sendMJEGFrames() ) if (!sendMJEGFrames())
zm_terminate = true; zm_terminate = true;
} else { } else {
if ( !sendRAWFrames() ) if (!sendRAWFrames())
zm_terminate = true; zm_terminate = true;
} }
} }
close(fd_lock); close(fd_lock);
} }

View File

@ -25,15 +25,10 @@ class Monitor;
class FifoStream : public StreamBase { class FifoStream : public StreamBase {
private: private:
char * stream_path; std::string stream_path;
int total_read; int total_read;
int bytes_read; int bytes_read;
unsigned int frame_count; unsigned int frame_count;
static void file_create_if_missing(
const char * path,
bool is_fifo,
bool delete_fake_fifo = true
);
protected: protected:
typedef enum { UNKNOWN, MJPEG, RAW } StreamType; typedef enum { UNKNOWN, MJPEG, RAW } StreamType;
@ -44,13 +39,13 @@ class FifoStream : public StreamBase {
public: public:
FifoStream() : FifoStream() :
stream_path(nullptr),
total_read(0), total_read(0),
bytes_read(0), bytes_read(0),
frame_count(0), frame_count(0),
stream_type(UNKNOWN) stream_type(UNKNOWN)
{} {}
void setStreamStart(const char * path);
void setStreamStart(const std::string &path);
void setStreamStart(int monitor_id, const char * format); void setStreamStart(int monitor_id, const char * format);
void runStream() override; void runStream() override;
}; };

View File

@ -21,7 +21,6 @@
#include "zm_packet.h" #include "zm_packet.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
FileCamera::FileCamera( FileCamera::FileCamera(
const Monitor *monitor, const Monitor *monitor,
@ -49,20 +48,20 @@ FileCamera::FileCamera(
p_capture, p_capture,
p_record_audio) p_record_audio)
{ {
strncpy( path, p_path, sizeof(path)-1 ); path = std::string(p_path);
if ( capture ) { if (capture) {
Initialise(); Initialise();
} }
} }
FileCamera::~FileCamera() { FileCamera::~FileCamera() {
if ( capture ) { if (capture) {
Terminate(); Terminate();
} }
} }
void FileCamera::Initialise() { void FileCamera::Initialise() {
if ( !path[0] ) { if (path.empty()) {
Fatal("No path specified for file image"); Fatal("No path specified for file image");
} }
} }
@ -71,9 +70,9 @@ void FileCamera::Terminate() {
} }
int FileCamera::PreCapture() { int FileCamera::PreCapture() {
struct stat statbuf; struct stat statbuf = {};
if ( stat(path, &statbuf) < 0 ) { if (stat(path.c_str(), &statbuf) < 0) {
Error("Can't stat %s: %s", path, strerror(errno)); Error("Can't stat %s: %s", path.c_str(), strerror(errno));
return -1; return -1;
} }
bytes += statbuf.st_size; bytes += statbuf.st_size;
@ -81,8 +80,8 @@ int FileCamera::PreCapture() {
// This waits until 1 second has passed since it was modified. Effectively limiting fps to 60. // This waits until 1 second has passed since it was modified. Effectively limiting fps to 60.
// Which is kinda bogus. If we were writing to this jpg constantly faster than we are monitoring it here // Which is kinda bogus. If we were writing to this jpg constantly faster than we are monitoring it here
// we would never break out of this loop // we would never break out of this loop
while ( (time(nullptr) - statbuf.st_mtime) < 1 ) { while ((time(nullptr) - statbuf.st_mtime) < 1) {
usleep(100000); std::this_thread::sleep_for(Milliseconds(100));
} }
return 0; return 0;
} }

View File

@ -27,26 +27,19 @@
// accessed using a single file which contains the latest jpeg data // accessed using a single file which contains the latest jpeg data
// //
class FileCamera : public Camera { class FileCamera : public Camera {
protected: public:
char path[PATH_MAX]; FileCamera(const Monitor *monitor,
const char *p_path,
public: int p_width,
FileCamera( int p_height,
const Monitor *monitor, int p_colours,
const char *p_path, int p_brightness,
int p_width, int p_contrast,
int p_height, int p_hue,
int p_colours, int p_colour,
int p_brightness, bool p_capture,
int p_contrast, bool p_record_audio);
int p_hue, ~FileCamera() override;
int p_colour,
bool p_capture,
bool p_record_audio
);
~FileCamera();
const char *Path() const { return path; }
void Initialise(); void Initialise();
void Terminate(); void Terminate();
@ -54,6 +47,11 @@ public:
int Capture(std::shared_ptr<ZMPacket> &p) override; int Capture(std::shared_ptr<ZMPacket> &p) override;
int PostCapture() override; int PostCapture() override;
int Close() override { return 0; }; int Close() override { return 0; };
const std::string &Path() const { return path; }
private:
std::string path;
}; };
#endif // ZM_FILE_CAMERA_H #endif // ZM_FILE_CAMERA_H

View File

@ -3,7 +3,7 @@
Frame::Frame(event_id_t p_event_id, Frame::Frame(event_id_t p_event_id,
int p_frame_id, int p_frame_id,
FrameType p_type, FrameType p_type,
struct timeval p_timestamp, SystemTimePoint p_timestamp,
Microseconds p_delta, Microseconds p_delta,
int p_score, int p_score,
std::vector<ZoneStats> p_stats) std::vector<ZoneStats> p_stats)

View File

@ -23,8 +23,6 @@
#include "zm_event.h" #include "zm_event.h"
#include "zm_time.h" #include "zm_time.h"
#include "zm_zone.h" #include "zm_zone.h"
#include <sys/time.h>
#include <vector> #include <vector>
enum FrameType { enum FrameType {
@ -41,7 +39,7 @@ class Frame {
Frame(event_id_t p_event_id, Frame(event_id_t p_event_id,
int p_frame_id, int p_frame_id,
FrameType p_type, FrameType p_type,
struct timeval p_timestamp, SystemTimePoint p_timestamp,
Microseconds p_delta, Microseconds p_delta,
int p_score, int p_score,
std::vector<ZoneStats> p_stats std::vector<ZoneStats> p_stats
@ -50,7 +48,7 @@ class Frame {
event_id_t event_id; event_id_t event_id;
int frame_id; int frame_id;
FrameType type; FrameType type;
struct timeval timestamp; SystemTimePoint timestamp;
Microseconds delta; Microseconds delta;
int score; int score;
std::vector<ZoneStats> zone_stats; std::vector<ZoneStats> zone_stats;

View File

@ -20,6 +20,7 @@
#include "zm_group.h" #include "zm_group.h"
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_utils.h"
#include <cstring> #include <cstring>
Group::Group() { Group::Group() {
@ -39,27 +40,27 @@ Group::Group(const MYSQL_ROW &dbrow) {
/* If a zero or invalid p_id is passed, then the old default path will be assumed. */ /* If a zero or invalid p_id is passed, then the old default path will be assumed. */
Group::Group(unsigned int p_id) { Group::Group(unsigned int p_id) {
id = 0; id = 0;
if ( p_id ) { if (p_id) {
char sql[ZM_SQL_SML_BUFSIZ]; std::string sql = stringtf("SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id);
snprintf(sql, sizeof(sql), "SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id); Debug(2, "Loading Group for %u using %s", p_id, sql.c_str());
Debug(2,"Loading Group for %u using %s", p_id, sql); zmDbRow dbrow;
zmDbRow dbrow; if (!dbrow.fetch(sql)) {
if ( !dbrow.fetch(sql) ) { Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn));
Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn)); } else {
} else { unsigned int index = 0;
unsigned int index = 0; id = atoi(dbrow[index++]);
id = atoi(dbrow[index++]); parent_id = dbrow[index] ? atoi(dbrow[index]) : 0;
parent_id = dbrow[index] ? atoi(dbrow[index]): 0; index++; index++;
strncpy(name, dbrow[index++], sizeof(name)-1); strncpy(name, dbrow[index++], sizeof(name) - 1);
Debug(1, "Loaded Group area %d '%s'", id, this->Name()); Debug(1, "Loaded Group area %d '%s'", id, this->Name());
} }
} }
if ( ! id ) { if (!id) {
Debug(1,"No id passed to Group constructor."); Debug(1, "No id passed to Group constructor.");
strcpy(name, "Default"); strcpy(name, "Default");
} }
} }
Group::~Group() { Group::~Group() {

View File

@ -313,6 +313,7 @@ bool Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *t
return false; return false;
} }
zm_dump_video_frame(temp_frame, "dest frame after convert"); zm_dump_video_frame(temp_frame, "dest frame after convert");
update_function_pointers();
return true; return true;
} // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame) } // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame)
@ -687,6 +688,7 @@ void Image::AssignDirect(
subpixelorder = p_subpixelorder; subpixelorder = p_subpixelorder;
pixels = width * height; pixels = width * height;
size = new_buffer_size; size = new_buffer_size;
update_function_pointers();
} // end void Image::AssignDirect } // end void Image::AssignDirect
void Image::Assign( void Image::Assign(
@ -788,6 +790,7 @@ void Image::Assign(const Image &image) {
linesize = image.linesize; linesize = image.linesize;
} }
update_function_pointers();
if ( image.buffer != buffer ) if ( image.buffer != buffer )
(*fptr_imgbufcpy)(buffer, image.buffer, size); (*fptr_imgbufcpy)(buffer, image.buffer, size);
} }
@ -933,125 +936,123 @@ bool Image::WriteRaw(const char *filename) const {
return true; return true;
} }
bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder) { bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) {
unsigned int new_width, new_height, new_colours, new_subpixelorder; unsigned int new_width, new_height, new_colours, new_subpixelorder;
struct jpeg_decompress_struct *cinfo = readjpg_dcinfo;
if ( !cinfo ) { if (!readjpg_dcinfo) {
cinfo = readjpg_dcinfo = new jpeg_decompress_struct; readjpg_dcinfo = new jpeg_decompress_struct;
cinfo->err = jpeg_std_error(&jpg_err.pub); readjpg_dcinfo->err = jpeg_std_error(&jpg_err.pub);
jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message; jpg_err.pub.emit_message = zm_jpeg_emit_message;
jpeg_create_decompress(cinfo); jpeg_create_decompress(readjpg_dcinfo);
} }
FILE *infile; FILE *infile;
if ( (infile = fopen(filename, "rb")) == nullptr ) { if ((infile = fopen(filename.c_str(), "rb")) == nullptr) {
Error("Can't open %s: %s", filename, strerror(errno)); Error("Can't open %s: %s", filename.c_str(), strerror(errno));
return false; return false;
} }
if ( setjmp(jpg_err.setjmp_buffer) ) { if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile); fclose(infile);
return false; return false;
} }
jpeg_stdio_src(cinfo, infile); jpeg_stdio_src(readjpg_dcinfo, infile);
jpeg_read_header(cinfo, TRUE); jpeg_read_header(readjpg_dcinfo, true);
if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { if ((readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3)) {
Error("Unexpected colours when reading jpeg image: %d", colours); Error("Unexpected colours when reading jpeg image: %d", colours);
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile); fclose(infile);
return false; return false;
} }
/* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* Check if the image has at least one huffman table defined. If not, use the standard ones */
/* This is required for the MJPEG capture palette of USB devices */ /* This is required for the MJPEG capture palette of USB devices */
if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { if (readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) {
zm_use_std_huff_tables(cinfo); zm_use_std_huff_tables(readjpg_dcinfo);
} }
new_width = cinfo->image_width; new_width = readjpg_dcinfo->image_width;
new_height = cinfo->image_height; new_height = readjpg_dcinfo->image_height;
if ( (width != new_width) || (height != new_height) ) { if ((width != new_width) || (height != new_height)) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height); Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height);
} }
switch ( p_colours ) { switch (p_colours) {
case ZM_COLOUR_GRAY8: case ZM_COLOUR_GRAY8:
cinfo->out_color_space = JCS_GRAYSCALE; readjpg_dcinfo->out_color_space = JCS_GRAYSCALE;
new_colours = ZM_COLOUR_GRAY8; new_colours = ZM_COLOUR_GRAY8;
new_subpixelorder = ZM_SUBPIX_ORDER_NONE; new_subpixelorder = ZM_SUBPIX_ORDER_NONE;
break; break;
case ZM_COLOUR_RGB32: case ZM_COLOUR_RGB32:
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
new_colours = ZM_COLOUR_RGB32; new_colours = ZM_COLOUR_RGB32;
if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
cinfo->out_color_space = JCS_EXT_BGRX; readjpg_dcinfo->out_color_space = JCS_EXT_BGRX;
new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; new_subpixelorder = ZM_SUBPIX_ORDER_BGRA;
} else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
cinfo->out_color_space = JCS_EXT_XRGB; readjpg_dcinfo->out_color_space = JCS_EXT_XRGB;
new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; new_subpixelorder = ZM_SUBPIX_ORDER_ARGB;
} else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
cinfo->out_color_space = JCS_EXT_XBGR; readjpg_dcinfo->out_color_space = JCS_EXT_XBGR;
new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; new_subpixelorder = ZM_SUBPIX_ORDER_ABGR;
} else { } else {
/* Assume RGBA */ /* Assume RGBA */
cinfo->out_color_space = JCS_EXT_RGBX; readjpg_dcinfo->out_color_space = JCS_EXT_RGBX;
new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; new_subpixelorder = ZM_SUBPIX_ORDER_RGBA;
} }
break; break;
#else #else
Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead.");
#endif #endif
case ZM_COLOUR_RGB24: case ZM_COLOUR_RGB24:
default: default:
new_colours = ZM_COLOUR_RGB24; new_colours = ZM_COLOUR_RGB24;
if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_BGR; readjpg_dcinfo->out_color_space = JCS_EXT_BGR;
new_subpixelorder = ZM_SUBPIX_ORDER_BGR; new_subpixelorder = ZM_SUBPIX_ORDER_BGR;
#else #else
Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead.");
cinfo->out_color_space = JCS_RGB; cinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif #endif
} else { } else {
/* Assume RGB */ /* Assume RGB */
/* /*
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB; cinfo->out_color_space = JCS_EXT_RGB;
#else #else
cinfo->out_color_space = JCS_RGB; cinfo->out_color_space = JCS_RGB;
#endif #endif
*/ */
cinfo->out_color_space = JCS_RGB; readjpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
} }
break; break;
} // end switch p_colours } // end switch p_colours
if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) {
Error("Failed requesting writeable buffer for reading JPEG image."); Error("Failed requesting writeable buffer for reading JPEG image.");
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile); fclose(infile);
return false; return false;
} }
jpeg_start_decompress(cinfo); jpeg_start_decompress(readjpg_dcinfo);
JSAMPROW row_pointer = buffer; JSAMPROW row_pointer = buffer;
while ( cinfo->output_scanline < cinfo->output_height ) { while (readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height) {
jpeg_read_scanlines(cinfo, &row_pointer, 1); jpeg_read_scanlines(readjpg_dcinfo, &row_pointer, 1);
row_pointer += linesize; row_pointer += linesize;
} }
jpeg_finish_decompress(cinfo); jpeg_finish_decompress(readjpg_dcinfo);
fclose(infile); fclose(infile);
return true; return true;
@ -1060,71 +1061,74 @@ cinfo->out_color_space = JCS_RGB;
// Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults. // Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults.
// Note quality=zero means default // Note quality=zero means default
bool Image::WriteJpeg(const char *filename, int quality_override) const { bool Image::WriteJpeg(const std::string &filename, int quality_override) const {
return Image::WriteJpeg(filename, quality_override, {}, false); return Image::WriteJpeg(filename, quality_override, {}, false);
} }
bool Image::WriteJpeg(const char *filename) const { bool Image::WriteJpeg(const std::string &filename) const {
return Image::WriteJpeg(filename, 0, {}, false); return Image::WriteJpeg(filename, 0, {}, false);
} }
bool Image::WriteJpeg(const char *filename, bool on_blocking_abort) const { bool Image::WriteJpeg(const std::string &filename, bool on_blocking_abort) const {
return Image::WriteJpeg(filename, 0, {}, on_blocking_abort); return Image::WriteJpeg(filename, 0, {}, on_blocking_abort);
} }
bool Image::WriteJpeg(const char *filename, SystemTimePoint timestamp) const { bool Image::WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const {
return Image::WriteJpeg(filename, 0, timestamp, false); return Image::WriteJpeg(filename, 0, timestamp, false);
} }
bool Image::WriteJpeg(const char *filename, int quality_override, SystemTimePoint timestamp) const { bool Image::WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const {
return Image::WriteJpeg(filename, quality_override, timestamp, false); return Image::WriteJpeg(filename, quality_override, timestamp, false);
} }
bool Image::WriteJpeg(const char *filename, bool Image::WriteJpeg(const std::string &filename,
int quality_override, const int &quality_override,
SystemTimePoint timestamp, SystemTimePoint timestamp,
bool on_blocking_abort) const { bool on_blocking_abort) const {
if ( config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8) ) { if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) {
Image temp_image(*this); Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort); return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort);
} }
int quality = quality_override ? quality_override : config.jpeg_file_quality; int quality = quality_override ? quality_override : config.jpeg_file_quality;
struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
FILE *outfile = nullptr; FILE *outfile = nullptr;
int raw_fd = 0; int raw_fd = 0;
if ( !cinfo ) { if (!cinfo) {
cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct;
cinfo->err = jpeg_std_error(&jpg_err.pub); cinfo->err = jpeg_std_error(&jpg_err.pub);
jpeg_create_compress(cinfo); jpeg_create_compress(cinfo);
} }
if ( !on_blocking_abort ) { if (!on_blocking_abort) {
jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message; jpg_err.pub.emit_message = zm_jpeg_emit_message;
} else { } else {
jpg_err.pub.error_exit = zm_jpeg_error_silent; jpg_err.pub.error_exit = zm_jpeg_error_silent;
jpg_err.pub.emit_message = zm_jpeg_emit_silence; jpg_err.pub.emit_message = zm_jpeg_emit_silence;
if ( setjmp(jpg_err.setjmp_buffer) ) { if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_compress(cinfo); jpeg_abort_compress(cinfo);
Debug(1, "Aborted a write mid-stream and %s and %d", (outfile == nullptr) ? "closing file" : "file not opened", raw_fd); Debug(1,
if ( raw_fd ) "Aborted a write mid-stream and %s and %d",
(outfile == nullptr) ? "closing file" : "file not opened",
raw_fd);
if (raw_fd)
close(raw_fd); close(raw_fd);
if ( outfile ) if (outfile)
fclose(outfile); fclose(outfile);
return false; return false;
} }
} }
if ( !on_blocking_abort ) { if (!on_blocking_abort) {
if ( (outfile = fopen(filename, "wb")) == nullptr ) { if ((outfile = fopen(filename.c_str(), "wb")) == nullptr) {
Error("Can't open %s for writing: %s", filename, strerror(errno)); Error("Can't open %s for writing: %s", filename.c_str(), strerror(errno));
return false; return false;
} }
} else { } else {
raw_fd = open(filename, O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); raw_fd = open(filename.c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if ( raw_fd < 0 ) if (raw_fd < 0)
return false; return false;
outfile = fdopen(raw_fd, "wb"); outfile = fdopen(raw_fd, "wb");
if ( outfile == nullptr ) { if (outfile == nullptr) {
close(raw_fd); close(raw_fd);
return false; return false;
} }
@ -1135,58 +1139,58 @@ bool Image::WriteJpeg(const char *filename,
cinfo->image_width = width; /* image width and height, in pixels */ cinfo->image_width = width; /* image width and height, in pixels */
cinfo->image_height = height; cinfo->image_height = height;
switch ( colours ) { switch (colours) {
case ZM_COLOUR_GRAY8: case ZM_COLOUR_GRAY8:
cinfo->input_components = 1; cinfo->input_components = 1;
cinfo->in_color_space = JCS_GRAYSCALE; cinfo->in_color_space = JCS_GRAYSCALE;
break; break;
case ZM_COLOUR_RGB32: case ZM_COLOUR_RGB32:
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->input_components = 4; cinfo->input_components = 4;
if ( subpixelorder == ZM_SUBPIX_ORDER_RGBA ) { if (subpixelorder == ZM_SUBPIX_ORDER_RGBA) {
cinfo->in_color_space = JCS_EXT_RGBX; cinfo->in_color_space = JCS_EXT_RGBX;
} else if ( subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { } else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
cinfo->in_color_space = JCS_EXT_BGRX; cinfo->in_color_space = JCS_EXT_BGRX;
} else if ( subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { } else if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
cinfo->in_color_space = JCS_EXT_XRGB; cinfo->in_color_space = JCS_EXT_XRGB;
} else if ( subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { } else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
cinfo->in_color_space = JCS_EXT_XBGR; cinfo->in_color_space = JCS_EXT_XBGR;
} else { } else {
Warning("Unknwon subpixelorder %d", subpixelorder); Warning("Unknwon subpixelorder %d", subpixelorder);
/* Assume RGBA */ /* Assume RGBA */
cinfo->in_color_space = JCS_EXT_RGBX; cinfo->in_color_space = JCS_EXT_RGBX;
} }
break; break;
#else #else
Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source");
jpeg_abort_compress(cinfo);
fclose(outfile);
return false;
#endif
case ZM_COLOUR_RGB24:
default:
cinfo->input_components = 3;
if (subpixelorder == ZM_SUBPIX_ORDER_BGR) {
#ifdef JCS_EXTENSIONS
cinfo->in_color_space = JCS_EXT_BGR;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source");
jpeg_abort_compress(cinfo); jpeg_abort_compress(cinfo);
fclose(outfile); fclose(outfile);
return false; return false;
#endif #endif
case ZM_COLOUR_RGB24: } else {
default: /* Assume RGB */
cinfo->input_components = 3; /*
if ( subpixelorder == ZM_SUBPIX_ORDER_BGR) {
#ifdef JCS_EXTENSIONS
cinfo->in_color_space = JCS_EXT_BGR;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source");
jpeg_abort_compress(cinfo);
fclose(outfile);
return false;
#endif
} else {
/* Assume RGB */
/*
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB; cinfo->out_color_space = JCS_EXT_RGB;
#else #else
cinfo->out_color_space = JCS_RGB; cinfo->out_color_space = JCS_RGB;
#endif #endif
*/ */
cinfo->in_color_space = JCS_RGB; cinfo->in_color_space = JCS_RGB;
} }
break; break;
} // end switch(colours) } // end switch(colours)
jpeg_set_defaults(cinfo); jpeg_set_defaults(cinfo);
@ -1194,7 +1198,7 @@ cinfo->out_color_space = JCS_RGB;
cinfo->dct_method = JDCT_FASTEST; cinfo->dct_method = JDCT_FASTEST;
jpeg_start_compress(cinfo, TRUE); jpeg_start_compress(cinfo, TRUE);
if ( config.add_jpeg_comments && !annotation_.empty() ) { if (config.add_jpeg_comments && !annotation_.empty()) {
jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast<const JOCTET *>(annotation_.c_str()), annotation_.size()); jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast<const JOCTET *>(annotation_.c_str()), annotation_.size());
} }
// If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal
@ -1218,19 +1222,19 @@ cinfo->out_color_space = JCS_RGB;
snprintf(msbuf, sizeof msbuf, "%06d", static_cast<int32>(ts_usec.count())); snprintf(msbuf, sizeof msbuf, "%06d", static_cast<int32>(ts_usec.count()));
unsigned char exiftimes[82] = { unsigned char exiftimes[82] = {
0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00,
0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00 }; 0xff, 0x00};
memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf, EXIFTIMES_LEN);
memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN); memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN);
jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes)); jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *) exiftimes, sizeof(exiftimes));
} }
JSAMPROW row_pointer = buffer; /* pointer to a single row */ JSAMPROW row_pointer = buffer; /* pointer to a single row */
while ( cinfo->next_scanline < cinfo->image_height ) { while (cinfo->next_scanline < cinfo->image_height) {
jpeg_write_scanlines(cinfo, &row_pointer, 1); jpeg_write_scanlines(cinfo, &row_pointer, 1);
row_pointer += linesize; row_pointer += linesize;
} }
@ -1240,121 +1244,116 @@ cinfo->out_color_space = JCS_RGB;
return true; return true;
} }
bool Image::DecodeJpeg( bool Image::DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder)
const JOCTET *inbuffer,
int inbuffer_size,
unsigned int p_colours,
unsigned int p_subpixelorder)
{ {
unsigned int new_width, new_height, new_colours, new_subpixelorder; unsigned int new_width, new_height, new_colours, new_subpixelorder;
struct jpeg_decompress_struct *cinfo = decodejpg_dcinfo;
if ( !cinfo ) { if (!decodejpg_dcinfo) {
cinfo = decodejpg_dcinfo = new jpeg_decompress_struct; decodejpg_dcinfo = new jpeg_decompress_struct;
cinfo->err = jpeg_std_error( &jpg_err.pub ); decodejpg_dcinfo->err = jpeg_std_error(&jpg_err.pub);
jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message; jpg_err.pub.emit_message = zm_jpeg_emit_message;
jpeg_create_decompress( cinfo ); jpeg_create_decompress(decodejpg_dcinfo);
} }
if ( setjmp(jpg_err.setjmp_buffer) ) { if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(decodejpg_dcinfo);
return false; return false;
} }
zm_jpeg_mem_src(cinfo, inbuffer, inbuffer_size); zm_jpeg_mem_src(decodejpg_dcinfo, inbuffer, inbuffer_size);
jpeg_read_header(cinfo, TRUE); jpeg_read_header(decodejpg_dcinfo, TRUE);
if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { if ((decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3)) {
Error("Unexpected colours when reading jpeg image: %d", colours); Error("Unexpected colours when reading jpeg image: %d", colours);
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(decodejpg_dcinfo);
return false; return false;
} }
/* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* Check if the image has at least one huffman table defined. If not, use the standard ones */
/* This is required for the MJPEG capture palette of USB devices */ /* This is required for the MJPEG capture palette of USB devices */
if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { if (decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) {
zm_use_std_huff_tables(cinfo); zm_use_std_huff_tables(decodejpg_dcinfo);
} }
new_width = cinfo->image_width; new_width = decodejpg_dcinfo->image_width;
new_height = cinfo->image_height; new_height = decodejpg_dcinfo->image_height;
if ( (width != new_width) || (height != new_height) ) { if ((width != new_width) || (height != new_height)) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u",
width, height, new_width, new_height); width, height, new_width, new_height);
} }
switch (p_colours) { switch (p_colours) {
case ZM_COLOUR_GRAY8: case ZM_COLOUR_GRAY8:
cinfo->out_color_space = JCS_GRAYSCALE; decodejpg_dcinfo->out_color_space = JCS_GRAYSCALE;
new_colours = ZM_COLOUR_GRAY8; new_colours = ZM_COLOUR_GRAY8;
new_subpixelorder = ZM_SUBPIX_ORDER_NONE; new_subpixelorder = ZM_SUBPIX_ORDER_NONE;
break; break;
case ZM_COLOUR_RGB32: case ZM_COLOUR_RGB32:
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
new_colours = ZM_COLOUR_RGB32; new_colours = ZM_COLOUR_RGB32;
if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
cinfo->out_color_space = JCS_EXT_BGRX; decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX;
new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; new_subpixelorder = ZM_SUBPIX_ORDER_BGRA;
} else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
cinfo->out_color_space = JCS_EXT_XRGB; decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB;
new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; new_subpixelorder = ZM_SUBPIX_ORDER_ARGB;
} else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
cinfo->out_color_space = JCS_EXT_XBGR; decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR;
new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; new_subpixelorder = ZM_SUBPIX_ORDER_ABGR;
} else { } else {
/* Assume RGBA */ /* Assume RGBA */
cinfo->out_color_space = JCS_EXT_RGBX; decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX;
new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; new_subpixelorder = ZM_SUBPIX_ORDER_RGBA;
} }
break; break;
#else #else
Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead.");
#endif #endif
case ZM_COLOUR_RGB24: case ZM_COLOUR_RGB24:
default: default:
new_colours = ZM_COLOUR_RGB24; new_colours = ZM_COLOUR_RGB24;
if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_BGR; decodejpg_dcinfo->out_color_space = JCS_EXT_BGR;
new_subpixelorder = ZM_SUBPIX_ORDER_BGR; new_subpixelorder = ZM_SUBPIX_ORDER_BGR;
#else #else
Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead.");
cinfo->out_color_space = JCS_RGB; cinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif #endif
} else { } else {
/* Assume RGB */ /* Assume RGB */
/* /*
#ifdef JCS_EXTENSIONS #ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB; cinfo->out_color_space = JCS_EXT_RGB;
#else #else
cinfo->out_color_space = JCS_RGB; cinfo->out_color_space = JCS_RGB;
#endif #endif
*/ */
cinfo->out_color_space = JCS_RGB; decodejpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB; new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
} }
break; break;
} // end switch } // end switch
if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) {
Error("Failed requesting writeable buffer for reading JPEG image."); Error("Failed requesting writeable buffer for reading JPEG image.");
jpeg_abort_decompress(cinfo); jpeg_abort_decompress(decodejpg_dcinfo);
return false; return false;
} }
jpeg_start_decompress(cinfo); jpeg_start_decompress(decodejpg_dcinfo);
JSAMPROW row_pointer = buffer; /* pointer to a single row */ JSAMPROW row_pointer = buffer; /* pointer to a single row */
while ( cinfo->output_scanline < cinfo->output_height ) { while (decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height) {
jpeg_read_scanlines(cinfo, &row_pointer, 1); jpeg_read_scanlines(decodejpg_dcinfo, &row_pointer, 1);
row_pointer += linesize; row_pointer += linesize;
} }
jpeg_finish_decompress(cinfo); jpeg_finish_decompress(decodejpg_dcinfo);
return true; return true;
} }

View File

@ -181,7 +181,7 @@ class Image {
/* Internal buffer should not be modified from functions outside of this class */ /* Internal buffer should not be modified from functions outside of this class */
inline const uint8_t* Buffer() const { return buffer; } inline const uint8_t* Buffer() const { return buffer; }
inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize)+x]; } inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize) + x*colours]; }
/* Request writeable buffer */ /* Request writeable buffer */
uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
// Is only acceptable on a pre-allocated buffer // Is only acceptable on a pre-allocated buffer
@ -233,14 +233,17 @@ class Image {
bool ReadRaw(const char *filename); bool ReadRaw(const char *filename);
bool WriteRaw(const char *filename) const; bool WriteRaw(const char *filename) const;
bool ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); bool ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder);
bool WriteJpeg(const char *filename) const; bool WriteJpeg(const std::string &filename) const;
bool WriteJpeg(const char *filename, bool on_blocking_abort) const; bool WriteJpeg(const std::string &filename, bool on_blocking_abort) const;
bool WriteJpeg(const char *filename, int quality_override) const; bool WriteJpeg(const std::string &filename, int quality_override) const;
bool WriteJpeg(const char *filename, SystemTimePoint timestamp) const; bool WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const;
bool WriteJpeg(const char *filename, int quality_override, SystemTimePoint timestamp) const; bool WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const;
bool WriteJpeg(const char *filename, int quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const; bool WriteJpeg(const std::string &filename,
const int &quality_override,
SystemTimePoint timestamp,
bool on_blocking_abort) const;
bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder);
bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const; bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const;

View File

@ -37,13 +37,13 @@ void zm_jpeg_emit_silence(j_common_ptr cinfo, int msg_level) {
} }
void zm_jpeg_error_exit(j_common_ptr cinfo) { void zm_jpeg_error_exit(j_common_ptr cinfo) {
static char buffer[JMSG_LENGTH_MAX]; zm_error_ptr zmerr = (zm_error_ptr) cinfo->err;
zm_error_ptr zmerr = (zm_error_ptr)cinfo->err;
(zmerr->pub.format_message)(cinfo, buffer); char buffer[JMSG_LENGTH_MAX];
zmerr->pub.format_message(cinfo, buffer);
Error("%s", buffer); Error("%s", buffer);
if ( ++jpeg_err_count == MAX_JPEG_ERRS ) { if (++jpeg_err_count == MAX_JPEG_ERRS) {
Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count); Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count);
} }
@ -51,25 +51,25 @@ void zm_jpeg_error_exit(j_common_ptr cinfo) {
} }
void zm_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { void zm_jpeg_emit_message(j_common_ptr cinfo, int msg_level) {
static char buffer[JMSG_LENGTH_MAX]; char buffer[JMSG_LENGTH_MAX];
zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; zm_error_ptr zmerr = (zm_error_ptr) cinfo->err;
if ( msg_level < 0 ) { if (msg_level < 0) {
/* It's a warning message. Since corrupt files may generate many warnings, /* It's a warning message. Since corrupt files may generate many warnings,
* the policy implemented here is to show only the first warning, * the policy implemented here is to show only the first warning,
* unless trace_level >= 3. * unless trace_level >= 3.
*/ */
if ( zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3 ) { if (zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3) {
(zmerr->pub.format_message)(cinfo, buffer); zmerr->pub.format_message(cinfo, buffer);
if ( !strstr(buffer, "Corrupt JPEG data:") ) if (!strstr(buffer, "Corrupt JPEG data:"))
Warning("%s", buffer); Warning("%s", buffer);
} }
/* Always count warnings in num_warnings. */ /* Always count warnings in num_warnings. */
zmerr->pub.num_warnings++; zmerr->pub.num_warnings++;
} else { } else {
/* It's a trace message. Show it if trace_level >= msg_level. */ /* It's a trace message. Show it if trace_level >= msg_level. */
if ( zmerr->pub.trace_level >= msg_level ) { if (zmerr->pub.trace_level >= msg_level) {
(zmerr->pub.format_message)(cinfo, buffer); zmerr->pub.format_message(cinfo, buffer);
Debug(msg_level, "%s", buffer); Debug(msg_level, "%s", buffer);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
#include "zm_camera.h" #include "zm_camera.h"
#if ZM_HAS_V4L #if ZM_HAS_V4L2
#include <linux/videodev2.h> #include <linux/videodev2.h>
@ -113,6 +113,7 @@ public:
int Palette() const { return palette; } int Palette() const { return palette; }
int Extras() const { return extras; } int Extras() const { return extras; }
int Control(int vid_id, int newvalue=-1 );
int Brightness( int p_brightness=-1 ) override; int Brightness( int p_brightness=-1 ) override;
int Hue( int p_hue=-1 ) override; int Hue( int p_hue=-1 ) override;
int Colour( int p_colour=-1 ) override; int Colour( int p_colour=-1 ) override;
@ -123,9 +124,9 @@ public:
int Capture(std::shared_ptr<ZMPacket> &p) override; int Capture(std::shared_ptr<ZMPacket> &p) override;
int PostCapture() override; int PostCapture() override;
int Close() override; int Close() override;
static bool GetCurrentSettings(const char *device, char *output, int version, bool verbose); static bool GetCurrentSettings(const std::string &device, char *output, int version, bool verbose);
}; };
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
#endif // ZM_LOCAL_CAMERA_H #endif // ZM_LOCAL_CAMERA_H

View File

@ -518,18 +518,14 @@ void Logger::logPrint(bool hex, const char *filepath, int line, int level, const
if (level <= mDatabaseLevel) { if (level <= mDatabaseLevel) {
if (zmDbConnected) { if (zmDbConnected) {
int syslogSize = syslogEnd - syslogStart; std::string escapedString = zmDbEscapeString({syslogStart, syslogEnd});
std::string escapedString;
escapedString.resize((syslogSize * 2) + 1);
mysql_real_escape_string(&dbconn, &escapedString[0], syslogStart, syslogSize);
escapedString.resize(std::strlen(escapedString.c_str()));
std::string sql_string = stringtf( std::string sql_string = stringtf(
"INSERT INTO `Logs` " "INSERT INTO `Logs` "
"( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )"
" VALUES " " VALUES "
"( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", "( %ld.%06" PRIi64 ", '%s', %d, %d, %d, '%s', '%s', '%s', %d )",
now_sec, now_frac.count(), mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, now_sec, static_cast<int64>(now_frac.count()), mId.c_str(), staticConfig.SERVER_ID, tid, level, classString,
escapedString.c_str(), file, line); escapedString.c_str(), file, line);
dbQueue.push(std::move(sql_string)); dbQueue.push(std::move(sql_string));
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@ public:
} Function; } Function;
typedef enum { typedef enum {
LOCAL, LOCAL=1,
REMOTE, REMOTE,
FILE, FILE,
FFMPEG, FFMPEG,
@ -313,15 +313,15 @@ protected:
int pre_event_count; // How many images to hold and prepend to an alarm event 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 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 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 Seconds section_length; // How long events should last in continuous modes
int min_section_length; // Minimum event length when using event_close_mode == ALARM Seconds min_section_length; // Minimum event length when using event_close_mode == ALARM
bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor 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 frame_skip; // How many frames to skip in continuous modes
int motion_frame_skip; // How many frames to skip in motion detection int motion_frame_skip; // How many frames to skip in motion detection
double analysis_fps_limit; // Target framerate for video analysis double analysis_fps_limit; // Target framerate for video analysis
unsigned int analysis_update_delay; // How long we wait before updating analysis parameters Microseconds analysis_update_delay; // How long we wait before updating analysis parameters
int capture_delay; // How long we wait between capture frames Microseconds capture_delay; // How long we wait between capture frames
int alarm_capture_delay; // How long we wait between capture frames when in alarm state Microseconds 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 alarm_frame_count; // How many alarm frames are required before an event is triggered
int alert_to_alarm_frame_count; // How many alarm frames (consecutive score frames) are required to return alarm from alert int alert_to_alarm_frame_count; // How many alarm frames (consecutive score frames) are required to return alarm from alert
// value for now is the same number configured in alarm_frame_count, maybe getting his own parameter some day // value for now is the same number configured in alarm_frame_count, maybe getting his own parameter some day
@ -424,7 +424,6 @@ protected:
public: public:
explicit Monitor(); explicit Monitor();
explicit Monitor(unsigned int p_id);
~Monitor(); ~Monitor();
@ -453,7 +452,7 @@ public:
inline unsigned int Id() const { return id; } inline unsigned int Id() const { return id; }
inline const char *Name() const { return name.c_str(); } inline const char *Name() const { return name.c_str(); }
inline unsigned int ServerId() { return server_id; } inline unsigned int ServerId() const { return server_id; }
inline Storage *getStorage() { inline Storage *getStorage() {
if ( ! storage ) { if ( ! storage ) {
storage = new Storage(storage_id); storage = new Storage(storage_id);
@ -486,7 +485,7 @@ public:
} }
inline bool Exif() const { return embed_exif; } inline bool Exif() const { return embed_exif; }
inline bool RTSPServer() const { return rtsp_server; } inline bool RTSPServer() const { return rtsp_server; }
inline bool RecordAudio() { return record_audio; } inline bool RecordAudio() const { return record_audio; }
/* /*
inline Purpose Purpose() { return purpose }; inline Purpose Purpose() { return purpose };
@ -513,8 +512,12 @@ public:
uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; }
void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; } void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; }
struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } SystemTimePoint GetVideoWriterStartTime() const {
void SetVideoWriterStartTime(const struct timeval &t) { video_store_data->recording = t; } return SystemTimePoint(zm::chrono::duration_cast<Microseconds>(video_store_data->recording));
}
void SetVideoWriterStartTime(SystemTimePoint t) {
video_store_data->recording = zm::chrono::duration_cast<timeval>(t.time_since_epoch());
}
unsigned int GetPreEventCount() const { return pre_event_count; }; unsigned int GetPreEventCount() const { return pre_event_count; };
int32_t GetImageBufferCount() const { return image_buffer_count; }; int32_t GetImageBufferCount() const { return image_buffer_count; };
@ -525,20 +528,20 @@ public:
AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; }; AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; };
AVCodecContext *GetVideoCodecContext() const { return camera ? camera->getVideoCodecContext() : nullptr; }; AVCodecContext *GetVideoCodecContext() const { return camera ? camera->getVideoCodecContext() : nullptr; };
const std::string GetSecondPath() const { return second_path; }; std::string GetSecondPath() const { return second_path; };
const std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; }; std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; };
const std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; }; std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; };
const std::string GetRTSPStreamName() const { return rtsp_streamname; }; std::string GetRTSPStreamName() const { return rtsp_streamname; };
int GetImage(int32_t index=-1, int scale=100); int GetImage(int32_t index=-1, int scale=100);
ZMPacket *getSnapshot( int index=-1 ) const; ZMPacket *getSnapshot( int index=-1 ) const;
struct timeval GetTimestamp( int index=-1 ) const; SystemTimePoint GetTimestamp(int index = -1) const;
void UpdateAdaptiveSkip(); void UpdateAdaptiveSkip();
useconds_t GetAnalysisRate(); useconds_t GetAnalysisRate();
unsigned int GetAnalysisUpdateDelay() const { return analysis_update_delay; } Microseconds GetAnalysisUpdateDelay() const { return analysis_update_delay; }
unsigned int GetCaptureMaxFPS() const { return capture_max_fps; } unsigned int GetCaptureMaxFPS() const { return capture_max_fps; }
int GetCaptureDelay() const { return capture_delay; } Microseconds GetCaptureDelay() const { return capture_delay; }
int GetAlarmCaptureDelay() const { return alarm_capture_delay; } Microseconds GetAlarmCaptureDelay() const { return alarm_capture_delay; }
unsigned int GetLastReadIndex() const; unsigned int GetLastReadIndex() const;
unsigned int GetLastWriteIndex() const; unsigned int GetLastWriteIndex() const;
uint64_t GetLastEventId() const; uint64_t GetLastEventId() const;
@ -549,9 +552,11 @@ public:
void ForceAlarmOff(); void ForceAlarmOff();
void CancelForced(); void CancelForced();
TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; } TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; }
inline time_t getStartupTime() const { return shared_data->startup_time; } SystemTimePoint GetStartupTime() const { return std::chrono::system_clock::from_time_t(shared_data->startup_time); }
inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; } void SetStartupTime(SystemTimePoint time) { shared_data->startup_time = std::chrono::system_clock::to_time_t(time); }
inline void setHeartbeatTime( time_t p_time ) { shared_data->zmc_heartbeat_time = p_time; } void SetHeartbeatTime(SystemTimePoint time) {
shared_data->zmc_heartbeat_time = std::chrono::system_clock::to_time_t(time);
}
void get_ref_image(); void get_ref_image();
int LabelSize() const { return label_size; } int LabelSize() const { return label_size; }
@ -562,10 +567,14 @@ public:
void actionSuspend(); void actionSuspend();
void actionResume(); void actionResume();
int actionBrightness( int p_brightness=-1 ); int actionBrightness(int p_brightness);
int actionHue( int p_hue=-1 ); int actionBrightness();
int actionColour( int p_colour=-1 ); int actionHue(int p_hue);
int actionContrast( int p_contrast=-1 ); int actionHue();
int actionColour(int p_colour);
int actionColour();
int actionContrast(int p_contrast);
int actionContrast();
int PrimeCapture(); int PrimeCapture();
int PreCapture() const; int PreCapture() const;
@ -582,7 +591,7 @@ public:
bool Analyse(); bool Analyse();
bool Decode(); bool Decode();
void DumpImage( Image *dump_image ) const; void DumpImage( Image *dump_image ) const;
void TimestampImage(Image *ts_image, const timeval &ts_time) const; void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const;
void closeEvent(); void closeEvent();
void Reload(); void Reload();
@ -595,9 +604,9 @@ public:
StringVector GroupNames(); StringVector GroupNames();
static std::vector<std::shared_ptr<Monitor>> LoadMonitors(const std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. static std::vector<std::shared_ptr<Monitor>> LoadMonitors(const std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure.
#if ZM_HAS_V4L #if ZM_HAS_V4L2
static std::vector<std::shared_ptr<Monitor>> LoadLocalMonitors(const char *device, Purpose purpose); static std::vector<std::shared_ptr<Monitor>> LoadLocalMonitors(const char *device, Purpose purpose);
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
static std::vector<std::shared_ptr<Monitor>> LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Purpose purpose); static std::vector<std::shared_ptr<Monitor>> LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Purpose purpose);
static std::vector<std::shared_ptr<Monitor>> LoadFileMonitors(const char *file, Purpose purpose); static std::vector<std::shared_ptr<Monitor>> LoadFileMonitors(const char *file, Purpose purpose);
static std::vector<std::shared_ptr<Monitor>> LoadFfmpegMonitors(const char *file, Purpose purpose); static std::vector<std::shared_ptr<Monitor>> LoadFfmpegMonitors(const char *file, Purpose purpose);
@ -614,7 +623,7 @@ public:
double get_analysis_fps( ) const { double get_analysis_fps( ) const {
return shared_data ? shared_data->analysis_fps : 0.0; return shared_data ? shared_data->analysis_fps : 0.0;
} }
int Importance() { return importance; } int Importance() const { return importance; }
}; };
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))

View File

@ -89,7 +89,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_PLAY : case CMD_PLAY :
Debug(1, "Got PLAY command"); Debug(1, "Got PLAY command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -97,7 +97,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_VARPLAY : case CMD_VARPLAY :
Debug(1, "Got VARPLAY command"); Debug(1, "Got VARPLAY command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -110,7 +110,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_FASTFWD : case CMD_FASTFWD :
Debug(1, "Got FAST FWD command"); Debug(1, "Got FAST FWD command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -135,27 +135,27 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} }
break; break;
case CMD_SLOWFWD : case CMD_SLOWFWD :
Debug( 1, "Got SLOW FWD command" ); Debug(1, "Got SLOW FWD command");
paused = true; paused = true;
delayed = true; delayed = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = 1; step = 1;
break; break;
case CMD_SLOWREV : case CMD_SLOWREV :
Debug( 1, "Got SLOW REV command" ); Debug(1, "Got SLOW REV command");
paused = true; paused = true;
delayed = true; delayed = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = -1; step = -1;
break; break;
case CMD_FASTREV : case CMD_FASTREV :
Debug( 1, "Got FAST REV command" ); Debug(1, "Got FAST REV command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
// Set play rate // Set play rate
switch ( replay_rate ) { switch (replay_rate) {
case -2 * ZM_RATE_BASE : case -2 * ZM_RATE_BASE :
replay_rate = -5 * ZM_RATE_BASE; replay_rate = -5 * ZM_RATE_BASE;
break; break;
@ -255,7 +255,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} status_data; } status_data;
status_data.id = monitor->Id(); status_data.id = monitor->Id();
if ( ! monitor->ShmValid() ) { if (!monitor->ShmValid()) {
status_data.fps = 0.0; status_data.fps = 0.0;
status_data.capture_fps = 0.0; status_data.capture_fps = 0.0;
status_data.analysis_fps = 0.0; status_data.analysis_fps = 0.0;
@ -265,7 +265,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.forced = false; status_data.forced = false;
status_data.buffer_level = 0; status_data.buffer_level = 0;
} else { } else {
status_data.fps = monitor->GetFPS(); FPSeconds elapsed = now - last_fps_update;
if (elapsed.count()) {
actual_fps = (frame_count - last_frame_count) / elapsed.count();
last_frame_count = frame_count;
last_fps_update = now;
}
status_data.fps = actual_fps;
status_data.capture_fps = monitor->get_capture_fps(); status_data.capture_fps = monitor->get_capture_fps();
status_data.analysis_fps = monitor->get_analysis_fps(); status_data.analysis_fps = monitor->get_analysis_fps();
status_data.state = monitor->shared_data->state; status_data.state = monitor->shared_data->state;
@ -320,7 +327,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
//updateFrameRate(monitor->GetFPS()); //updateFrameRate(monitor->GetFPS());
} // end void MonitorStream::processCommand(const CmdMsg *msg) } // end void MonitorStream::processCommand(const CmdMsg *msg)
bool MonitorStream::sendFrame(const char *filepath, SystemTimePoint timestamp) { bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) {
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
if ( if (
@ -330,19 +337,18 @@ bool MonitorStream::sendFrame(const char *filepath, SystemTimePoint timestamp) {
) )
send_raw = false; send_raw = false;
if ( !send_raw ) { if (!send_raw) {
Image temp_image(filepath); Image temp_image(filepath.c_str());
return sendFrame(&temp_image, timestamp); return sendFrame(&temp_image, timestamp);
} else { } else {
int img_buffer_size = 0; int img_buffer_size = 0;
static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE];
FILE *fdj = nullptr; if (FILE *fdj = fopen(filepath.c_str(), "r")) {
if ( (fdj = fopen(filepath, "r")) ) {
img_buffer_size = fread(img_buffer, 1, sizeof(img_buffer), fdj); img_buffer_size = fread(img_buffer, 1, sizeof(img_buffer), fdj);
fclose(fdj); fclose(fdj);
} else { } else {
Error("Can't open %s: %s", filepath, strerror(errno)); Error("Can't open %s: %s", filepath.c_str(), strerror(errno));
return false; return false;
} }
@ -377,12 +383,12 @@ bool MonitorStream::sendFrame(const char *filepath, SystemTimePoint timestamp) {
return true; return true;
} }
return false; return false;
} // end bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) }
bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
Image *send_image = prepareImage(image); Image *send_image = prepareImage(image);
if (!config.timestamp_on_capture) { if (!config.timestamp_on_capture) {
monitor->TimestampImage(send_image, zm::chrono::duration_cast<timeval>(timestamp.time_since_epoch())); monitor->TimestampImage(send_image, timestamp);
} }
fputs("--" BOUNDARY "\r\n", stdout); fputs("--" BOUNDARY "\r\n", stdout);
@ -461,15 +467,17 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
} // Not mpeg } // Not mpeg
last_frame_sent = now; last_frame_sent = now;
return true; return true;
} // end bool MonitorStream::sendFrame( Image *image, const timeval &timestamp ) }
void MonitorStream::runStream() { void MonitorStream::runStream() {
if (type == STREAM_SINGLE) { if (type == STREAM_SINGLE) {
// Not yet migrated over to stream class // Not yet migrated over to stream class
if (checkInitialised()) if (checkInitialised()) {
SingleImage(scale); SingleImage(scale);
else } else {
fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout);
sendTextFrame("Unable to send image"); sendTextFrame("Unable to send image");
}
return; return;
} }
@ -518,7 +526,7 @@ void MonitorStream::runStream() {
Image *paused_image = nullptr; Image *paused_image = nullptr;
SystemTimePoint paused_timestamp; SystemTimePoint paused_timestamp;
if ( connkey && ( playback_buffer > 0 ) ) { if (connkey && (playback_buffer > 0)) {
// 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
const int max_swap_len_suffix = 15; const int max_swap_len_suffix = 15;
@ -527,27 +535,27 @@ void MonitorStream::runStream() {
int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1;
int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;
if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { if (total_swap_path_length + max_swap_len_suffix > PATH_MAX) {
Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX);
} else { } else {
swap_path = staticConfig.PATH_SWAP; swap_path = staticConfig.PATH_SWAP;
Debug(3, "Checking swap path folder: %s", swap_path.c_str()); Debug(3, "Checking swap path folder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
swap_path += stringtf("/zmswap-m%d", monitor->Id()); swap_path += stringtf("/zmswap-m%d", monitor->Id());
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
swap_path += stringtf("/zmswap-q%06d", connkey); swap_path += stringtf("/zmswap-q%06d", connkey);
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
buffered_playback = true; buffered_playback = true;
} }
} }
} }
if ( !buffered_playback ) { if (!buffered_playback) {
Error("Unable to validate swap image path, disabling buffered playback"); Error("Unable to validate swap image path, disabling buffered playback");
} else { } else {
Debug(2, "Assigning temporary buffer"); Debug(2, "Assigning temporary buffer");
@ -557,14 +565,13 @@ void MonitorStream::runStream() {
} }
} else { } else {
Debug(2, "Not using playback_buffer"); Debug(2, "Not using playback_buffer");
} // end if connkey & playback_buffer } // end if connkey && playback_buffer
while (!zm_terminate) { while (!zm_terminate) {
bool got_command = false; if (feof(stdout)) {
if ( feof(stdout) ) {
Debug(2, "feof stdout"); Debug(2, "feof stdout");
break; break;
} else if ( ferror(stdout) ) { } else if (ferror(stdout)) {
Debug(2, "ferror stdout"); Debug(2, "ferror stdout");
break; break;
} else if (!monitor->ShmValid()) { } else if (!monitor->ShmValid()) {
@ -575,8 +582,9 @@ void MonitorStream::runStream() {
now = std::chrono::system_clock::now(); now = std::chrono::system_clock::now();
bool was_paused = paused; bool was_paused = paused;
if ( connkey ) { bool got_command = false; // commands like zoom should output a frame even if paused
while ( checkCommandQueue() && !zm_terminate ) { if (connkey) {
while (checkCommandQueue() && !zm_terminate) {
// Loop in here until all commands are processed. // Loop in here until all commands are processed.
Debug(2, "Have checking command Queue for connkey: %d", connkey); Debug(2, "Have checking command Queue for connkey: %d", connkey);
got_command = true; got_command = true;
@ -588,35 +596,31 @@ void MonitorStream::runStream() {
} }
} // end if connkey } // end if connkey
if ( paused ) { if (paused) {
if ( !was_paused ) { if (!was_paused) {
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
Debug(1, "Saving paused image from index %d",index); Debug(1, "Saving paused image from index %d",index);
paused_image = new Image(*monitor->image_buffer[index]); paused_image = new Image(*monitor->image_buffer[index]);
paused_timestamp = SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])); paused_timestamp = SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index]));
} }
} else if ( paused_image ) { } else if (paused_image) {
Debug(1, "Clearing paused_image");
delete paused_image; delete paused_image;
paused_image = nullptr; paused_image = nullptr;
} }
if ( buffered_playback && delayed ) { if (buffered_playback && delayed) {
if ( temp_read_index == temp_write_index ) { if (temp_read_index == temp_write_index) {
// Go back to live viewing // Go back to live viewing
Debug(1, "Exceeded temporary streaming buffer"); Debug(1, "Exceeded temporary streaming buffer");
// Clear paused flag
paused = false; paused = false;
// Clear delayed_play flag
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
} else { } else {
if ( !paused ) { if (!paused) {
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count);
// Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index );
SwapImage *swap_image = &temp_image_buffer[temp_index]; SwapImage *swap_image = &temp_image_buffer[temp_index];
if ( !swap_image->valid ) { if (!swap_image->valid) {
paused = true; paused = true;
delayed = true; delayed = true;
temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count);
@ -627,12 +631,13 @@ void MonitorStream::runStream() {
// If the next frame is due // If the next frame is due
if (actual_delta_time > expected_delta_time) { if (actual_delta_time > expected_delta_time) {
// Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); // Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time );
if (temp_index % frame_mod == 0) { if ((temp_index % frame_mod) == 0) {
Debug(2, "Sending delayed frame %d", temp_index); Debug(2, "Sending delayed frame %d", temp_index);
// Send the next frame // Send the next frame
if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) {
zm_terminate = true; zm_terminate = true;
} }
frame_count++;
last_frame_timestamp = swap_image->timestamp; last_frame_timestamp = swap_image->timestamp;
// frame_sent = true; // frame_sent = true;
} }
@ -645,9 +650,13 @@ void MonitorStream::runStream() {
SwapImage *swap_image = &temp_image_buffer[temp_read_index]; SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame // Send the next frame
if (!sendFrame(temp_image_buffer[temp_read_index].file_name, temp_image_buffer[temp_read_index].timestamp)) { if (!sendFrame(
temp_image_buffer[temp_read_index].file_name,
temp_image_buffer[temp_read_index].timestamp)
) {
zm_terminate = true; zm_terminate = true;
} }
frame_count++;
last_frame_timestamp = swap_image->timestamp; last_frame_timestamp = swap_image->timestamp;
// frame_sent = true; // frame_sent = true;
@ -663,12 +672,13 @@ void MonitorStream::runStream() {
if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) {
zm_terminate = true; zm_terminate = true;
} }
frame_count++;
// frame_sent = true; // frame_sent = true;
} }
} // end if (!paused) or step or paused } // end if (!paused) or step or paused
} // end if have exceeded buffer or not } // end if have exceeded buffer or not
if ( temp_read_index == temp_write_index ) { if (temp_read_index == temp_write_index) {
// Go back to live viewing // Go back to live viewing
Warning("Rewound over write index, resuming live play"); Warning("Rewound over write index, resuming live play");
// Clear paused flag // Clear paused flag
@ -677,13 +687,13 @@ void MonitorStream::runStream() {
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
} }
} // end if ( buffered_playback && delayed ) } // end if (buffered_playback && delayed)
if ( last_read_index != monitor->shared_data->last_write_index ) { if (last_read_index != monitor->shared_data->last_write_index) {
// have a new image to send // have a new image to send
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ((frame_mod == 1) || ((frame_count%frame_mod) == 0)) {
if ( !paused && !delayed ) { if (!paused && !delayed) {
last_read_index = monitor->shared_data->last_write_index; last_read_index = monitor->shared_data->last_write_index;
Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
index, frame_mod, frame_count, paused, delayed); index, frame_mod, frame_count, paused, delayed);
@ -699,8 +709,7 @@ void MonitorStream::runStream() {
zm_terminate = true; zm_terminate = true;
break; break;
} }
//frame_sent = true; frame_count++;
//
if (frame_count == 0) { if (frame_count == 0) {
// Chrome will not display the first frame until it receives another. // Chrome will not display the first frame until it receives another.
// Firefox is fine. So just send the first frame twice. // Firefox is fine. So just send the first frame twice.
@ -713,16 +722,18 @@ void MonitorStream::runStream() {
temp_read_index = temp_write_index; temp_read_index = temp_write_index;
} else { } else {
if ( delayed && !buffered_playback ) { if (delayed && !buffered_playback) {
Debug(2, "Can't delay when not buffering."); Debug(2, "Can't delay when not buffering.");
delayed = false; delayed = false;
} }
if ( last_zoom != zoom ) { if (last_zoom != zoom) {
Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom); Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom);
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
frame_count++;
frame_count++;
} else { } else {
SystemTimePoint::duration actual_delta_time = now - last_frame_sent; SystemTimePoint::duration actual_delta_time = now - last_frame_sent;
if (actual_delta_time > Seconds(5)) { if (actual_delta_time > Seconds(5)) {
@ -733,37 +744,32 @@ void MonitorStream::runStream() {
// Send the next frame // Send the next frame
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
frame_count++;
} else { } else {
Debug(2, "Would have sent keepalive frame, but had no paused_image"); Debug(2, "Would have sent keepalive frame, but had no paused_image");
} }
} // end if actual_delta_time > 5 } // end if actual_delta_time > 5
} // end if change in zoom } // end if change in zoom
} // end if paused or not } // end if paused or not
} else {
frame_count++;
} // end if should send frame } // end if should send frame
if ( buffered_playback && !paused ) { if (buffered_playback && !paused) {
if ( monitor->shared_data->valid ) { if (monitor->shared_data->valid) {
if ( monitor->shared_timestamps[index].tv_sec ) { if (monitor->shared_timestamps[index].tv_sec) {
int temp_index = temp_write_index%temp_image_buffer_count; int temp_index = temp_write_index%temp_image_buffer_count;
Debug(2, "Storing frame %d", temp_index); Debug(2, "Storing frame %d", temp_index);
if ( !temp_image_buffer[temp_index].valid ) { if ( !temp_image_buffer[temp_index].valid ) {
snprintf( temp_image_buffer[temp_index].file_name = stringtf("%s/zmswap-i%05d.jpg", swap_path.c_str(), temp_index);
temp_image_buffer[temp_index].file_name,
sizeof(temp_image_buffer[0].file_name),
"%s/zmswap-i%05d.jpg",
swap_path.c_str(),
temp_index);
temp_image_buffer[temp_index].valid = true; temp_image_buffer[temp_index].valid = true;
} }
temp_image_buffer[temp_index].timestamp = temp_image_buffer[temp_index].timestamp =
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])); SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index]));
monitor->image_buffer[index]->WriteJpeg( monitor->image_buffer[index]->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality);
temp_image_buffer[temp_index].file_name,
config.jpeg_file_quality
);
temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count);
if ( temp_write_index == temp_read_index ) { if (temp_write_index == temp_read_index) {
// Go back to live viewing // Go back to live viewing
Warning("Exceeded temporary buffer, resuming live play"); Warning("Exceeded temporary buffer, resuming live play");
paused = false; paused = false;
@ -777,7 +783,6 @@ void MonitorStream::runStream() {
Warning("Unable to store frame as shared memory invalid"); Warning("Unable to store frame as shared memory invalid");
} }
} // end if buffered playback } // end if buffered playback
frame_count++;
} else { } else {
Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
@ -808,38 +813,37 @@ void MonitorStream::runStream() {
Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)", Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)",
frame_mod, frame_count); frame_mod, frame_count);
} }
} // end while } // end while ! zm_terminate
if ( buffered_playback ) { if (buffered_playback) {
Debug(1, "Cleaning swap files from %s", swap_path.c_str()); Debug(1, "Cleaning swap files from %s", swap_path.c_str());
struct stat stat_buf; struct stat stat_buf = {};
if ( stat(swap_path.c_str(), &stat_buf) < 0 ) { if (stat(swap_path.c_str(), &stat_buf) < 0) {
if ( errno != ENOENT ) { if (errno != ENOENT) {
Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno)); Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno));
} }
} else if ( !S_ISDIR(stat_buf.st_mode) ) { } else if (!S_ISDIR(stat_buf.st_mode)) {
Error("Swap image path '%s' is not a directory", swap_path.c_str()); Error("Swap image path '%s' is not a directory", swap_path.c_str());
} else { } else {
char glob_pattern[PATH_MAX] = ""; std::string glob_pattern = stringtf("%s/*.*", swap_path.c_str());
snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str());
glob_t pglob; glob_t pglob;
int glob_status = glob(glob_pattern, 0, 0, &pglob);
if ( glob_status != 0 ) { int glob_status = glob(glob_pattern.c_str(), 0, 0, &pglob);
if ( glob_status < 0 ) { if (glob_status != 0) {
Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); if (glob_status < 0) {
Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno));
} else { } else {
Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status);
} }
} else { } else {
for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { for (unsigned int i = 0; i < pglob.gl_pathc; i++) {
if ( unlink(pglob.gl_pathv[i]) < 0 ) { if (unlink(pglob.gl_pathv[i]) < 0) {
Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno)); Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno));
} }
} }
} }
globfree(&pglob); globfree(&pglob);
if ( rmdir(swap_path.c_str()) < 0 ) { if (rmdir(swap_path.c_str()) < 0) {
Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno)); Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno));
} }
} // end if checking for swap_path } // end if checking for swap_path
@ -854,7 +858,7 @@ void MonitorStream::SingleImage(int scale) {
Image scaled_image; Image scaled_image;
while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) {
Debug(1, "Waiting for capture to begin"); Debug(1, "Waiting for capture to begin");
usleep(100000); std::this_thread::sleep_for(Milliseconds(100));
} }
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index); Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index);
@ -865,8 +869,9 @@ void MonitorStream::SingleImage(int scale) {
scaled_image.Scale(scale); scaled_image.Scale(scale);
snap_image = &scaled_image; snap_image = &scaled_image;
} }
if ( !config.timestamp_on_capture ) { if (!config.timestamp_on_capture) {
monitor->TimestampImage(snap_image, monitor->shared_timestamps[index]); monitor->TimestampImage(snap_image,
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])));
} }
snap_image->EncodeJpeg(img_buffer, &img_buffer_size); snap_image->EncodeJpeg(img_buffer, &img_buffer_size);

View File

@ -21,14 +21,13 @@
#define ZM_MONITORSTREAM_H #define ZM_MONITORSTREAM_H
#include "zm_stream.h" #include "zm_stream.h"
#include <sys/time.h>
class MonitorStream : public StreamBase { class MonitorStream : public StreamBase {
protected: protected:
struct SwapImage { struct SwapImage {
bool valid = false; bool valid = false;
SystemTimePoint timestamp; SystemTimePoint timestamp;
char file_name[PATH_MAX] = ""; std::string file_name;
}; };
private: private:
@ -41,11 +40,10 @@ class MonitorStream : public StreamBase {
Microseconds ttl; Microseconds ttl;
int playback_buffer; int playback_buffer;
bool delayed; bool delayed;
int frame_count;
protected: protected:
bool checkSwapPath(const char *path, bool create_path); bool checkSwapPath(const char *path, bool create_path);
bool sendFrame(const char *filepath, SystemTimePoint timestamp); bool sendFrame(const std::string &filepath, SystemTimePoint timestamp);
bool sendFrame(Image *image, SystemTimePoint timestamp); bool sendFrame(Image *image, SystemTimePoint timestamp);
void processCommand(const CmdMsg *msg) override; void processCommand(const CmdMsg *msg) override;
void SingleImage(int scale=100); void SingleImage(int scale=100);
@ -62,8 +60,8 @@ class MonitorStream : public StreamBase {
temp_write_index(0), temp_write_index(0),
ttl(0), ttl(0),
playback_buffer(0), playback_buffer(0),
delayed(false), delayed(false)
frame_count(0) {} {}
void setStreamBuffer(int p_playback_buffer) { void setStreamBuffer(int p_playback_buffer) {
playback_buffer = p_playback_buffer; playback_buffer = p_playback_buffer;

View File

@ -21,13 +21,7 @@
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_rgb.h" #include "zm_rgb.h"
#include <cstring> #include "zm_time.h"
#include <unistd.h>
extern "C" {
#include <libavutil/mathematics.h>
#include <libavcodec/avcodec.h>
}
bool VideoStream::initialised = false; bool VideoStream::initialised = false;
@ -537,32 +531,29 @@ int VideoStream::SendPacket(AVPacket *packet) {
return ret; return ret;
} }
void *VideoStream::StreamingThreadCallback(void *ctx){ void *VideoStream::StreamingThreadCallback(void *ctx) {
Debug(1, "StreamingThreadCallback started");
Debug( 1, "StreamingThreadCallback started" );
if (ctx == nullptr) return nullptr;
VideoStream* videoStream = reinterpret_cast<VideoStream*>(ctx); if (ctx == nullptr) {
return nullptr;
}
const uint64_t nanosecond_multiplier = 1000000000; VideoStream *videoStream = reinterpret_cast<VideoStream *>(ctx);
uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); TimePoint::duration target_interval = std::chrono::duration_cast<TimePoint::duration>(FPSeconds(
videoStream->codec_context->time_base.num / static_cast<double>(videoStream->codec_context->time_base.den)));
uint64_t frame_count = 0; uint64_t frame_count = 0;
timespec start_time; TimePoint start_time = std::chrono::steady_clock::now();
clock_gettime(CLOCK_MONOTONIC, &start_time);
uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; while (videoStream->do_streaming) {
while(videoStream->do_streaming) { TimePoint current_time = std::chrono::steady_clock::now();
timespec current_time; TimePoint target = start_time + (target_interval * frame_count);
clock_gettime(CLOCK_MONOTONIC, &current_time);
uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; if (current_time < target) {
uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); // It's not time to render a frame yet.
std::this_thread::sleep_for(target - current_time);
if ( current_time_ns < target_ns ) { }
// It's not time to render a frame yet.
usleep( (target_ns - current_time_ns) * 0.001 );
}
// By sending the last rendered frame we deliver frames to the client more accurate. // By sending the last rendered frame we deliver frames to the client more accurate.
// If we're encoding the frame before sending it there will be lag. // If we're encoding the frame before sending it there will be lag.
@ -573,27 +564,29 @@ void *VideoStream::StreamingThreadCallback(void *ctx){
if (packet->size) { if (packet->size) {
videoStream->SendPacket(packet); videoStream->SendPacket(packet);
} }
av_packet_unref( packet); av_packet_unref(packet);
videoStream->packet_index = videoStream->packet_index ? 0 : 1; videoStream->packet_index = videoStream->packet_index ? 0 : 1;
// Lock buffer and render next frame. // Lock buffer and render next frame.
if (pthread_mutex_lock(videoStream->buffer_copy_lock) != 0) {
if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { Fatal("StreamingThreadCallback: pthread_mutex_lock failed.");
Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); }
}
if (videoStream->buffer_copy) {
if ( videoStream->buffer_copy ) { // Encode next frame.
// Encode next frame. videoStream->ActuallyEncodeFrame(videoStream->buffer_copy,
videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); videoStream->buffer_copy_used,
} videoStream->add_timestamp,
videoStream->timestamp);
if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { }
Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." );
} if (pthread_mutex_unlock(videoStream->buffer_copy_lock) != 0) {
Fatal("StreamingThreadCallback: pthread_mutex_unlock failed.");
frame_count++; }
}
frame_count++;
return nullptr; }
return nullptr;
} }

View File

@ -21,7 +21,6 @@
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_image.h" #include "zm_image.h"
#include "zm_logger.h" #include "zm_logger.h"
#include <sys/time.h>
using namespace std; using namespace std;
AVPixelFormat target_format = AV_PIX_FMT_NONE; AVPixelFormat target_format = AV_PIX_FMT_NONE;
@ -31,7 +30,6 @@ ZMPacket::ZMPacket() :
stream(nullptr), stream(nullptr),
in_frame(nullptr), in_frame(nullptr),
out_frame(nullptr), out_frame(nullptr),
timestamp({}),
buffer(nullptr), buffer(nullptr),
image(nullptr), image(nullptr),
analysis_image(nullptr), analysis_image(nullptr),
@ -40,13 +38,13 @@ ZMPacket::ZMPacket() :
image_index(-1), image_index(-1),
codec_imgsize(0), codec_imgsize(0),
pts(0), pts(0),
decoded(0) decoded(false)
{ {
av_init_packet(&packet); av_init_packet(&packet);
packet.size = 0; // So we can detect whether it has been filled. packet.size = 0; // So we can detect whether it has been filled.
} }
ZMPacket::ZMPacket(Image *i, const timeval &tv) : ZMPacket::ZMPacket(Image *i, SystemTimePoint tv) :
keyframe(0), keyframe(0),
stream(nullptr), stream(nullptr),
in_frame(nullptr), in_frame(nullptr),
@ -60,7 +58,7 @@ ZMPacket::ZMPacket(Image *i, const timeval &tv) :
image_index(-1), image_index(-1),
codec_imgsize(0), codec_imgsize(0),
pts(0), pts(0),
decoded(0) decoded(false)
{ {
av_init_packet(&packet); av_init_packet(&packet);
packet.size = 0; // So we can detect whether it has been filled. packet.size = 0; // So we can detect whether it has been filled.
@ -80,12 +78,12 @@ ZMPacket::ZMPacket(ZMPacket &p) :
image_index(-1), image_index(-1),
codec_imgsize(0), codec_imgsize(0),
pts(0), pts(0),
decoded(0) decoded(false)
{ {
av_init_packet(&packet); av_init_packet(&packet);
packet.size = 0; packet.size = 0;
packet.data = nullptr; packet.data = nullptr;
if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) { if (zm_av_packet_ref(&packet, &p.packet) < 0) {
Error("error refing packet"); Error("error refing packet");
} }
} }
@ -95,8 +93,8 @@ ZMPacket::~ZMPacket() {
if (in_frame) av_frame_free(&in_frame); if (in_frame) av_frame_free(&in_frame);
if (out_frame) av_frame_free(&out_frame); if (out_frame) av_frame_free(&out_frame);
if (buffer) av_freep(&buffer); if (buffer) av_freep(&buffer);
if (analysis_image) delete analysis_image; delete analysis_image;
if (image) delete image; delete image;
} }
/* returns < 0 on error, 0 on not ready, int bytes consumed on success /* returns < 0 on error, 0 on not ready, int bytes consumed on success
@ -243,8 +241,8 @@ AVPacket *ZMPacket::set_packet(AVPacket *p) {
if (zm_av_packet_ref(&packet, p) < 0) { if (zm_av_packet_ref(&packet, p) < 0) {
Error("error refing packet"); Error("error refing packet");
} }
//ZM_DUMP_PACKET(packet, "zmpacket:");
gettimeofday(&timestamp, nullptr); timestamp = std::chrono::system_clock::now();
keyframe = p->flags & AV_PKT_FLAG_KEY; keyframe = p->flags & AV_PKT_FLAG_KEY;
return &packet; return &packet;
} }

View File

@ -21,6 +21,7 @@
#define ZM_PACKET_H #define ZM_PACKET_H
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_time.h"
#include "zm_zone.h" #include "zm_zone.h"
#include <condition_variable> #include <condition_variable>
@ -31,10 +32,6 @@ extern "C" {
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
} }
#ifdef __FreeBSD__
#include <sys/time.h>
#endif // __FreeBSD__
class Image; class Image;
class ZMPacket { class ZMPacket {
@ -49,7 +46,7 @@ class ZMPacket {
AVPacket packet; // Input packet, undecoded AVPacket packet; // Input packet, undecoded
AVFrame *in_frame; // Input image, decoded Theoretically only filled if needed. AVFrame *in_frame; // Input image, decoded Theoretically only filled if needed.
AVFrame *out_frame; // output image, Only filled if needed. AVFrame *out_frame; // output image, Only filled if needed.
timeval timestamp; SystemTimePoint timestamp;
uint8_t *buffer; // buffer used in image uint8_t *buffer; // buffer used in image
Image *image; Image *image;
Image *analysis_image; Image *analysis_image;
@ -69,8 +66,8 @@ class ZMPacket {
Image *set_image(Image *); Image *set_image(Image *);
int is_keyframe() { return keyframe; }; int is_keyframe() { return keyframe; };
int decode( AVCodecContext *ctx ); int decode(AVCodecContext *ctx);
explicit ZMPacket(Image *image, const timeval &tv); explicit ZMPacket(Image *image, SystemTimePoint tv);
explicit ZMPacket(ZMPacket &packet); explicit ZMPacket(ZMPacket &packet);
ZMPacket(); ZMPacket();
~ZMPacket(); ~ZMPacket();
@ -91,6 +88,7 @@ class ZMLockedPacket {
lck_(packet_->mutex_, std::defer_lock), lck_(packet_->mutex_, std::defer_lock),
locked(false) { locked(false) {
} }
~ZMLockedPacket() { ~ZMLockedPacket() {
if (locked) unlock(); if (locked) unlock();
} }

View File

@ -24,7 +24,6 @@
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_packet.h" #include "zm_packet.h"
#include "zm_signal.h" #include "zm_signal.h"
#include <sys/time.h>
PacketQueue::PacketQueue(): PacketQueue::PacketQueue():
video_stream_id(-1), video_stream_id(-1),
@ -93,8 +92,10 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
" The queue is full. Either Analysis is not keeping up or" " The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting." " your camera's keyframe interval is larger than this setting."
, max_video_packet_count); , max_video_packet_count);
while (packet_counts[video_stream_id] > max_video_packet_count) { while (packet_counts[video_stream_id] > max_video_packet_count) {
Debug(1, "Capture waiting for room in the queue."); Error("Unable to free up older packets. Waiting.");
condition.notify_all();
condition.wait(lck); condition.wait(lck);
if (deleting or zm_terminate) if (deleting or zm_terminate)
return false; return false;
@ -153,6 +154,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count, add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count,
( *(pktQueue.begin()) != add_packet ) ( *(pktQueue.begin()) != add_packet )
); );
Warning("Keyframe interval may be larger than MaxImageBufferCount and PreEventCount. Please increase MaxImageBufferCount");
return; return;
} }
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
@ -620,3 +622,12 @@ void PacketQueue::setPreEventVideoPackets(int p) {
pre_event_video_packet_count = 1; pre_event_video_packet_count = 1;
// We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue
} }
void PacketQueue::notify_all() {
condition.notify_all();
};
void PacketQueue::wait() {
std::unique_lock<std::mutex> lck(mutex);
condition.wait(lck);
}

View File

@ -81,6 +81,8 @@ class PacketQueue {
); );
bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr<ZMPacket> &zm_packet); bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr<ZMPacket> &zm_packet);
void unlock(ZMLockedPacket *lp); void unlock(ZMLockedPacket *lp);
void notify_all();
void wait();
}; };
#endif /* ZM_PACKETQUEUE_H */ #endif /* ZM_PACKETQUEUE_H */

View File

@ -49,45 +49,43 @@ RemoteCamera::RemoteCamera(
mNeedAuth(false), mNeedAuth(false),
mAuthenticator(nullptr) mAuthenticator(nullptr)
{ {
if ( path[0] != '/' ) if (path[0] != '/')
path = '/'+path; path = '/'+path;
} }
RemoteCamera::~RemoteCamera() { RemoteCamera::~RemoteCamera() {
if ( hp != nullptr ) { if (hp != nullptr) {
freeaddrinfo(hp); freeaddrinfo(hp);
hp = nullptr; hp = nullptr;
} }
if ( mAuthenticator ) { if (mAuthenticator) {
delete mAuthenticator; delete mAuthenticator;
mAuthenticator = nullptr; mAuthenticator = nullptr;
} }
} }
void RemoteCamera::Initialise() { void RemoteCamera::Initialise() {
if( protocol.empty() ) if (protocol.empty())
Fatal( "No protocol specified for remote camera" ); Fatal("No protocol specified for remote camera");
if( host.empty() ) if (host.empty())
Fatal( "No host specified for remote camera" ); Fatal("No host specified for remote camera");
if ( port.empty() ) if (port.empty())
Fatal("No port specified for remote camera"); Fatal("No port specified for remote camera");
//if( path.empty() )
//Fatal( "No path specified for remote camera" );
// Cache as much as we can to speed things up // Cache as much as we can to speed things up
std::string::size_type authIndex = host.rfind( '@' ); std::string::size_type authIndex = host.rfind('@');
if ( authIndex != std::string::npos ) { if (authIndex != std::string::npos) {
auth = host.substr( 0, authIndex ); auth = host.substr(0, authIndex);
host.erase( 0, authIndex+1 ); host.erase(0, authIndex+1);
auth64 = Base64Encode(auth); auth64 = Base64Encode(auth);
authIndex = auth.rfind( ':' ); authIndex = auth.rfind(':');
username = auth.substr(0,authIndex); username = auth.substr(0,authIndex);
password = auth.substr( authIndex+1, auth.length() ); password = auth.substr(authIndex+1, auth.length());
} }
mNeedAuth = false; mNeedAuth = false;
@ -99,12 +97,13 @@ void RemoteCamera::Initialise() {
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp);
if ( ret != 0 ) { if (ret != 0) {
Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); Error("Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret));
return;
} }
struct addrinfo *p = nullptr; struct addrinfo *p = nullptr;
int addr_count = 0; int addr_count = 0;
for ( p = hp; p != nullptr; p = p->ai_next ) { for (p = hp; p != nullptr; p = p->ai_next) {
addr_count++; addr_count++;
} }
Debug(1, "%d addresses returned", addr_count); Debug(1, "%d addresses returned", addr_count);

View File

@ -144,6 +144,14 @@ void RemoteCameraHttp::Initialise() {
int RemoteCameraHttp::Connect() { int RemoteCameraHttp::Connect() {
struct addrinfo *p = nullptr; struct addrinfo *p = nullptr;
if (!hp) {
RemoteCamera::Initialise();
if (!hp) {
Error("Unable to resolve address for remote camera, aborting");
return -1;
}
}
for ( p = hp; p != nullptr; p = p->ai_next ) { for ( p = hp; p != nullptr; p = p->ai_next ) {
sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol );
if ( sd < 0 ) { if ( sd < 0 ) {
@ -300,15 +308,17 @@ int RemoteCameraHttp::ReadData(Buffer &buffer, unsigned int bytes_expected) {
} // end readData } // end readData
int RemoteCameraHttp::GetData() { int RemoteCameraHttp::GetData() {
time_t start_time = time(nullptr); TimePoint start_time = std::chrono::steady_clock::now();
int buffer_len = 0; int buffer_len;
while (!(buffer_len = ReadData(buffer))) { while (!(buffer_len = ReadData(buffer))) {
if (zm_terminate or ( (time(nullptr) - start_time) > ZM_WATCH_MAX_DELAY )) if (zm_terminate or std::chrono::steady_clock::now() - start_time > FPSeconds(config.watch_max_delay)) {
return -1; return -1;
Debug(4, "Timeout waiting for REGEXP HEADER"); }
usleep(100000);
} Debug(4, "Timeout waiting for REGEXP HEADER");
return buffer_len; std::this_thread::sleep_for(Milliseconds(100));
}
return buffer_len;
} }
int RemoteCameraHttp::GetResponse() { int RemoteCameraHttp::GetResponse() {

View File

@ -126,10 +126,11 @@ int RemoteCameraRtsp::Disconnect() {
int RemoteCameraRtsp::PrimeCapture() { int RemoteCameraRtsp::PrimeCapture() {
Debug(2, "Waiting for sources"); Debug(2, "Waiting for sources");
for ( int i = 0; (i < 100) && !rtspThread->hasSources(); i++ ) { for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) {
usleep(100000); std::this_thread::sleep_for(Microseconds(100));
} }
if ( !rtspThread->hasSources() ) {
if (!rtspThread->hasSources()) {
Error("No RTSP sources"); Error("No RTSP sources");
return -1; return -1;
} }

View File

@ -269,16 +269,16 @@ void RtpCtrlThread::Run() {
// The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets. // The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets.
// Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response // Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response
zm::Select select(10 ); zm::Select select(Seconds(10));
select.addReader( &rtpCtrlServer ); select.addReader( &rtpCtrlServer );
unsigned char buffer[ZM_NETWORK_BUFSIZ]; unsigned char buffer[ZM_NETWORK_BUFSIZ];
time_t last_receive = time(nullptr); TimePoint last_receive = std::chrono::steady_clock::now();
bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true. bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true.
while (!mTerminate && select.wait() >= 0) { while (!mTerminate && select.wait() >= 0) {
time_t now = time(nullptr); TimePoint now = std::chrono::steady_clock::now();
zm::Select::CommsList readable = select.getReadable(); zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) { if ( readable.size() == 0 ) {
if ( ! timeout ) { if ( ! timeout ) {
@ -287,20 +287,20 @@ void RtpCtrlThread::Run() {
unsigned char *bufferPtr = buffer; unsigned char *bufferPtr = buffer;
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %" PRIi64, Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %.2f s",
bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), static_cast<int64>(now - last_receive)); bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), FPSeconds(now - last_receive).count());
if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 ) if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 )
Error("Unable to send: %s", strerror(errno)); Error("Unable to send: %s", strerror(errno));
timeout = true; timeout = true;
continue; continue;
} else { } else {
Debug(1, "RTCP timed out. Time since last receive: %" PRIi64, static_cast<int64>(now - last_receive)); Debug(1, "RTCP timed out. Time since last receive: %.2f s", FPSeconds(now - last_receive).count());
continue; continue;
//break; //break;
} }
} else { } else {
timeout = false; timeout = false;
last_receive = time(nullptr); last_receive = std::chrono::steady_clock::now();
} }
for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) {
if ( zm::UdpInetSocket *socket = dynamic_cast<zm::UdpInetSocket *>(*iter) ) { if ( zm::UdpInetSocket *socket = dynamic_cast<zm::UdpInetSocket *>(*iter) ) {

View File

@ -76,7 +76,7 @@ void RtpDataThread::Run() {
} }
Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()); Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort());
zm::Select select(3); zm::Select select(Seconds(3));
select.addReader(&rtpDataSocket); select.addReader(&rtpDataSocket);
unsigned char buffer[ZM_NETWORK_BUFSIZ]; unsigned char buffer[ZM_NETWORK_BUFSIZ];

View File

@ -66,7 +66,7 @@ RtpSource::RtpSource(
mRtpFactor = mRtpClock; mRtpFactor = mRtpClock;
mBaseTimeReal = tvNow(); mBaseTimeReal = std::chrono::system_clock::now();
mBaseTimeNtp = {}; mBaseTimeNtp = {};
mBaseTimeRtp = rtpTime; mBaseTimeRtp = rtpTime;
@ -159,12 +159,9 @@ bool RtpSource::updateSeq(uint16_t seq) {
} }
void RtpSource::updateJitter( const RtpDataHeader *header ) { void RtpSource::updateJitter( const RtpDataHeader *header ) {
if ( mRtpFactor > 0 ) { if (mRtpFactor > 0) {
timeval now = {}; SystemTimePoint now = std::chrono::system_clock::now();
gettimeofday(&now, nullptr); FPSeconds time_diff = std::chrono::duration_cast<FPSeconds>(now - mBaseTimeReal);
FPSeconds time_diff =
zm::chrono::duration_cast<Microseconds>(now) - zm::chrono::duration_cast<Microseconds>(mBaseTimeReal);
uint32_t localTimeRtp = mBaseTimeRtp + static_cast<uint32>(time_diff.count() * mRtpFactor); uint32_t localTimeRtp = mBaseTimeRtp + static_cast<uint32>(time_diff.count() * mRtpFactor);
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN); uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
@ -202,7 +199,7 @@ void RtpSource::updateRtcpData(
Debug(5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); Debug(5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime);
if ( mBaseTimeNtp.tv_sec == 0 ) { if ( mBaseTimeNtp.tv_sec == 0 ) {
mBaseTimeReal = tvNow(); mBaseTimeReal = std::chrono::system_clock::now();
mBaseTimeNtp = ntpTime; mBaseTimeNtp = ntpTime;
mBaseTimeRtp = rtpTime; mBaseTimeRtp = rtpTime;
} else if ( !mRtpClock ) { } else if ( !mRtpClock ) {

View File

@ -24,6 +24,7 @@
#include "zm_config.h" #include "zm_config.h"
#include "zm_define.h" #include "zm_define.h"
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_time.h"
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -68,7 +69,7 @@ private:
// Time keys // Time keys
uint32_t mRtpClock; uint32_t mRtpClock;
uint32_t mRtpFactor; uint32_t mRtpFactor;
struct timeval mBaseTimeReal; SystemTimePoint mBaseTimeReal;
struct timeval mBaseTimeNtp; struct timeval mBaseTimeNtp;
uint32_t mBaseTimeRtp; uint32_t mBaseTimeRtp;

View File

@ -86,9 +86,8 @@ bool RtspThread::recvResponse(std::string &response) {
int RtspThread::requestPorts() { int RtspThread::requestPorts() {
if ( !smMinDataPort ) { if ( !smMinDataPort ) {
char sql[ZM_SQL_SML_BUFSIZ];
//FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors //FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors
strncpy(sql, "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC", sizeof(sql)); std::string sql = "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC";
MYSQL_RES *result = zmDbFetch(sql); MYSQL_RES *result = zmDbFetch(sql);
@ -331,7 +330,8 @@ void RtspThread::Run() {
authTried = true; authTried = true;
sendCommand(message); sendCommand(message);
// FIXME Why sleep 1? // FIXME Why sleep 1?
usleep(10000); std::this_thread::sleep_for(Microseconds(10));
res = recvResponse(response); res = recvResponse(response);
if ( !res && respCode==401 ) if ( !res && respCode==401 )
mNeedAuth = true; mNeedAuth = true;
@ -438,15 +438,18 @@ void RtspThread::Run() {
lines = Split(response, "\r\n"); lines = Split(response, "\r\n");
std::string session; std::string session;
int timeout = 0; Seconds timeout = Seconds(0);
char transport[256] = ""; char transport[256] = "";
for ( size_t i = 0; i < lines.size(); i++ ) { for ( size_t i = 0; i < lines.size(); i++ ) {
if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) { if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) {
StringVector sessionLine = Split(lines[i].substr(9), ";"); StringVector sessionLine = Split(lines[i].substr(9), ";");
session = TrimSpaces(sessionLine[0]); session = TrimSpaces(sessionLine[0]);
if ( sessionLine.size() == 2 ) if ( sessionLine.size() == 2 ){
sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); int32 timeout_val = 0;
sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val);
timeout = Seconds(timeout_val);
}
} }
sscanf(lines[i].c_str(), "Transport: %s", transport); sscanf(lines[i].c_str(), "Transport: %s", transport);
} }
@ -454,7 +457,9 @@ void RtspThread::Run() {
if ( session.empty() ) if ( session.empty() )
Fatal("Unable to get session identifier from response '%s'", response.c_str()); Fatal("Unable to get session identifier from response '%s'", response.c_str());
Debug(2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout); Debug(2, "Got RTSP session %s, timeout %" PRIi64 " secs",
session.c_str(),
static_cast<int64>(Seconds(timeout).count()));
if ( !transport[0] ) if ( !transport[0] )
Fatal("Unable to get transport details from response '%s'", response.c_str()); Fatal("Unable to get transport details from response '%s'", response.c_str());
@ -517,12 +522,18 @@ void RtspThread::Run() {
if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) ) if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) )
rtpInfo = TrimSpaces(lines[i].substr(9)); rtpInfo = TrimSpaces(lines[i].substr(9));
// Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent
if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) && ( timeout == 0 ) ) { if ((lines[i].size() > 8) && (lines[i].substr(0, 8) == "Session:") && (timeout == Seconds(0))) {
StringVector sessionLine = Split(lines[i].substr(9), ";"); StringVector sessionLine = Split(lines[i].substr(9), ";");
if ( sessionLine.size() == 2 ) if ( sessionLine.size() == 2 ){
sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); int32 timeout_val = 0;
if ( timeout > 0 ) sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val);
Debug(2, "Got timeout %d secs from PLAY command response", timeout); timeout = Seconds(timeout_val);
}
if ( timeout > Seconds(0) ) {
Debug(2, "Got timeout %" PRIi64 " secs from PLAY command response",
static_cast<int64>(Seconds(timeout).count()));
}
} }
} }
@ -557,8 +568,8 @@ void RtspThread::Run() {
Debug( 2, "RTSP Seq is %d", seq ); Debug( 2, "RTSP Seq is %d", seq );
Debug( 2, "RTSP Rtptime is %ld", rtpTime ); Debug( 2, "RTSP Rtptime is %ld", rtpTime );
time_t lastKeepalive = time(nullptr); TimePoint lastKeepalive = std::chrono::steady_clock::now();
time_t now; TimePoint now;
message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
switch( mMethod ) { switch( mMethod ) {
@ -570,20 +581,21 @@ void RtspThread::Run() {
RtpCtrlThread rtpCtrlThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source );
while (!mTerminate) { while (!mTerminate) {
now = time(nullptr); now = std::chrono::steady_clock::now();
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 "s ",
sendKeepalive, sendKeepalive,
timeout, static_cast<int64>(Seconds(timeout).count()),
static_cast<int64>(now), static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()),
static_cast<int64>(lastKeepalive), static_cast<int64>(std::chrono::duration_cast<Seconds>(lastKeepalive.time_since_epoch()).count()),
static_cast<int64>(now - lastKeepalive)); static_cast<int64>(std::chrono::duration_cast<Seconds>((now - lastKeepalive)).count()));
if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) {
if ( !sendCommand( message ) ) if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) {
if (!sendCommand(message))
return; return;
lastKeepalive = now; lastKeepalive = now;
} }
usleep( 100000 ); std::this_thread::sleep_for(Microseconds(100));
} }
#if 0 #if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
@ -621,7 +633,7 @@ void RtspThread::Run() {
RtpDataThread rtpDataThread( *this, *source ); RtpDataThread rtpDataThread( *this, *source );
RtpCtrlThread rtpCtrlThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source );
zm::Select select(double(config.http_timeout)/1000.0 ); zm::Select select(Milliseconds(config.http_timeout));
select.addReader( &mRtspSocket ); select.addReader( &mRtspSocket );
Buffer buffer( ZM_NETWORK_BUFSIZ ); Buffer buffer( ZM_NETWORK_BUFSIZ );
@ -694,21 +706,23 @@ void RtspThread::Run() {
} }
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
// FIXME: Is this really necessary when using tcp ? // FIXME: Is this really necessary when using tcp ?
now = time(nullptr); now = std::chrono::steady_clock::now();
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 " s",
sendKeepalive, sendKeepalive,
timeout, static_cast<int64>(Seconds(timeout).count()),
static_cast<int64>(now), static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()),
static_cast<int64>(lastKeepalive), static_cast<int64>(std::chrono::duration_cast<Seconds>(lastKeepalive.time_since_epoch()).count()),
static_cast<int64>(now - lastKeepalive)); static_cast<int64>(std::chrono::duration_cast<Seconds>((now - lastKeepalive)).count()));
if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) )
{ if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) {
if ( !sendCommand( message ) ) if (!sendCommand(message)) {
return; return;
}
lastKeepalive = now; lastKeepalive = now;
} }
buffer.tidy( 1 ); buffer.tidy(true);
} }
#if 0 #if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
@ -737,12 +751,14 @@ void RtspThread::Run() {
while (!mTerminate) { while (!mTerminate) {
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration // Send a keepalive message if the server supports this feature and we are close to the timeout expiration
if ( sendKeepalive && (timeout > 0) && ((time(nullptr)-lastKeepalive) > (timeout-5)) ) { if (sendKeepalive && (timeout > Seconds(0))
if ( !sendCommand( message ) ) && ((std::chrono::steady_clock::now() - lastKeepalive) > (timeout - Seconds(5)))) {
if (!sendCommand(message)) {
return; return;
lastKeepalive = time(nullptr); }
lastKeepalive = std::chrono::steady_clock::now();
} }
usleep(100000); std::this_thread::sleep_for(Microseconds(100));
} }
#if 0 #if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";

View File

@ -269,9 +269,8 @@ int main(int argc, char *argv[]) {
Warning("Unknown format in %s", videoFifoPath.c_str()); Warning("Unknown format in %s", videoFifoPath.c_str());
} }
if (videoSource == nullptr) { if (videoSource == nullptr) {
Error("Unable to create source"); Error("Unable to create source for %s", videoFifoPath.c_str());
rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId());
delete sessions[monitor->Id()];
sessions.erase(monitor->Id()); sessions.erase(monitor->Id());
continue; continue;
} }

View File

@ -151,7 +151,7 @@ int ZoneMinderFifoSource::getNextFrame() {
} }
} }
int bytes_read = m_buffer.read_into(m_fd, 32); int bytes_read = m_buffer.read_into(m_fd, 4096);
//int bytes_read = m_buffer.read_into(m_fd, 4096, {1,0}); //int bytes_read = m_buffer.read_into(m_fd, 4096, {1,0});
if (bytes_read == 0) { if (bytes_read == 0) {
Debug(3, "No bytes read"); Debug(3, "No bytes read");

View File

@ -21,32 +21,33 @@
#include "zm_db.h" #include "zm_db.h"
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_utils.h"
#include <cstring> #include <cstring>
Storage::Storage() : id(0) { Storage::Storage() : id(0) {
Warning("Instantiating default Storage Object. Should not happen."); Warning("Instantiating default Storage Object. Should not happen.");
strcpy(name, "Default"); strcpy(name, "Default");
if ( staticConfig.DIR_EVENTS[0] != '/' ) { if (staticConfig.DIR_EVENTS[0] != '/') {
// not using an absolute path. Make it one by appending ZM_PATH_WEB // not using an absolute path. Make it one by appending ZM_PATH_WEB
snprintf(path, sizeof(path), "%s/%s", snprintf(path, sizeof(path), "%s/%s",
staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str());
} else { } else {
strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1);
} }
scheme = MEDIUM; scheme = MEDIUM;
scheme_str = "Medium"; scheme_str = "Medium";
} }
Storage::Storage(MYSQL_ROW &dbrow) { Storage::Storage(MYSQL_ROW &dbrow) {
unsigned int index = 0; unsigned int index = 0;
id = atoi(dbrow[index++]); id = atoi(dbrow[index++]);
strncpy(name, dbrow[index++], sizeof(name)-1); strncpy(name, dbrow[index++], sizeof(name) - 1);
strncpy(path, dbrow[index++], sizeof(path)-1); strncpy(path, dbrow[index++], sizeof(path) - 1);
type_str = std::string(dbrow[index++]); type_str = std::string(dbrow[index++]);
scheme_str = std::string(dbrow[index++]); scheme_str = std::string(dbrow[index++]);
if ( scheme_str == "Deep" ) { if (scheme_str == "Deep") {
scheme = DEEP; scheme = DEEP;
} else if ( scheme_str == "Medium" ) { } else if (scheme_str == "Medium") {
scheme = MEDIUM; scheme = MEDIUM;
} else { } else {
scheme = SHALLOW; scheme = SHALLOW;
@ -55,44 +56,42 @@ Storage::Storage(MYSQL_ROW &dbrow) {
/* If a zero or invalid p_id is passed, then the old default path will be assumed. */ /* If a zero or invalid p_id is passed, then the old default path will be assumed. */
Storage::Storage(unsigned int p_id) : id(p_id) { Storage::Storage(unsigned int p_id) : id(p_id) {
if (id) {
if ( id ) { std::string sql = stringtf("SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id);
char sql[ZM_SQL_SML_BUFSIZ]; Debug(2, "Loading Storage for %u using %s", id, sql.c_str());
snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id); zmDbRow dbrow;
Debug(2, "Loading Storage for %u using %s", id, sql); if (!dbrow.fetch(sql)) {
zmDbRow dbrow; Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn));
if ( !dbrow.fetch(sql) ) { } else {
Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn)); unsigned int index = 0;
} else { id = atoi(dbrow[index++]);
unsigned int index = 0; strncpy(name, dbrow[index++], sizeof(name) - 1);
id = atoi(dbrow[index++]); strncpy(path, dbrow[index++], sizeof(path) - 1);
strncpy(name, dbrow[index++], sizeof(name)-1);
strncpy(path, dbrow[index++], sizeof(path)-1);
type_str = std::string(dbrow[index++]); type_str = std::string(dbrow[index++]);
scheme_str = std::string(dbrow[index++]); scheme_str = std::string(dbrow[index++]);
if ( scheme_str == "Deep" ) { if (scheme_str == "Deep") {
scheme = DEEP; scheme = DEEP;
} else if ( scheme_str == "Medium" ) { } else if (scheme_str == "Medium") {
scheme = MEDIUM; scheme = MEDIUM;
} else { } else {
scheme = SHALLOW; scheme = SHALLOW;
} }
Debug(1, "Loaded Storage area %d '%s'", id, name); Debug(1, "Loaded Storage area %d '%s'", id, name);
} }
} }
if ( !id ) { if (!id) {
if ( staticConfig.DIR_EVENTS[0] != '/' ) { if (staticConfig.DIR_EVENTS[0] != '/') {
// not using an absolute path. Make it one by appending ZM_PATH_WEB // not using an absolute path. Make it one by appending ZM_PATH_WEB
snprintf(path, sizeof(path), "%s/%s", snprintf(path, sizeof(path), "%s/%s",
staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str());
} else { } else {
strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path) - 1);
} }
Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); Debug(1, "No id passed to Storage constructor. Using default path %s instead", path);
strcpy(name, "Default"); strcpy(name, "Default");
scheme = MEDIUM; scheme = MEDIUM;
scheme_str = "Medium"; scheme_str = "Medium";
} }
} }
Storage::~Storage() { Storage::~Storage() {

View File

@ -254,18 +254,32 @@ Image *StreamBase::prepareImage(Image *image) {
} // end Image *StreamBase::prepareImage(Image *image) } // end Image *StreamBase::prepareImage(Image *image)
bool StreamBase::sendTextFrame(const char *frame_text) { bool StreamBase::sendTextFrame(const char *frame_text) {
Debug(2, "Sending %dx%d * %d text frame '%s'", int width = 640;
monitor->Width(), monitor->Height(), scale, frame_text); int height = 480;
int colours = ZM_COLOUR_RGB32;
int subpixelorder = ZM_SUBPIX_ORDER_RGBA;
int labelsize = 2;
Image image(monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder()); if (monitor) {
image.Clear(); width = monitor->Width();
image.Annotate(frame_text, image.centreCoord(frame_text, monitor->LabelSize()), monitor->LabelSize()); height = monitor->Height();
colours = monitor->Colours();
if ( scale != 100 ) { subpixelorder = monitor->SubpixelOrder();
image.Scale(scale); labelsize = monitor->LabelSize();
} }
if ( type == STREAM_MPEG ) { Debug(2, "Sending %dx%dx%dx%d * %d scale text frame '%s'",
if ( !vid_stream ) { width, height, colours, subpixelorder, scale, frame_text);
Image image(width, height, colours, subpixelorder);
image.Clear();
image.Annotate(frame_text, image.centreCoord(frame_text, labelsize), labelsize);
if (scale != 100) {
image.Scale(scale);
Debug(2, "Scaled to %dx%d", image.Width(), image.Height());
}
if (type == STREAM_MPEG) {
if (!vid_stream) {
vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, image.Colours(), image.SubpixelOrder(), image.Width(), image.Height()); vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, image.Colours(), image.SubpixelOrder(), image.Width(), image.Height());
fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType());
vid_stream->OpenStream(); vid_stream->OpenStream();
@ -276,10 +290,11 @@ bool StreamBase::sendTextFrame(const char *frame_text) {
int n_bytes = 0; int n_bytes = 0;
image.EncodeJpeg(buffer, &n_bytes); image.EncodeJpeg(buffer, &n_bytes);
Debug(4, "Encoded to %d bytes", n_bytes);
fputs("--" BOUNDARY "\r\nContent-Type: image/jpeg\r\n", stdout); fputs("--" BOUNDARY "\r\nContent-Type: image/jpeg\r\n", stdout);
fprintf(stdout, "Content-Length: %d\r\n\r\n", n_bytes); fprintf(stdout, "Content-Length: %d\r\n\r\n", n_bytes);
if ( fwrite(buffer, n_bytes, 1, stdout) != 1 ) { if (fwrite(buffer, n_bytes, 1, stdout) != 1) {
Error("Unable to send stream text frame: %s", strerror(errno)); Error("Unable to send stream text frame: %s", strerror(errno));
return false; return false;
} }

View File

@ -102,7 +102,6 @@ protected:
int last_scale; int last_scale;
int zoom; int zoom;
int last_zoom; int last_zoom;
double maxfps;
int bitrate; int bitrate;
unsigned short last_x, last_y; unsigned short last_x, last_y;
unsigned short x, y; unsigned short x, y;
@ -122,8 +121,14 @@ protected:
SystemTimePoint now; SystemTimePoint now;
SystemTimePoint last_comm_update; SystemTimePoint last_comm_update;
double base_fps; double maxfps;
double effective_fps; double base_fps; // Should be capturing fps, hence a rough target
double effective_fps; // Target fps after taking max_fps into account
double actual_fps; // sliding calculated actual streaming fps achieved
SystemTimePoint last_fps_update;
int frame_count; // Count of frames sent
int last_frame_count; // Used in calculating actual_fps from frame_count - last_frame_count
int frame_mod; int frame_mod;
SystemTimePoint last_frame_sent; SystemTimePoint last_frame_sent;
@ -152,7 +157,6 @@ public:
last_scale(DEFAULT_SCALE), last_scale(DEFAULT_SCALE),
zoom(DEFAULT_ZOOM), zoom(DEFAULT_ZOOM),
last_zoom(DEFAULT_ZOOM), last_zoom(DEFAULT_ZOOM),
maxfps(DEFAULT_MAXFPS),
bitrate(DEFAULT_BITRATE), bitrate(DEFAULT_BITRATE),
last_x(0), last_x(0),
last_y(0), last_y(0),
@ -164,7 +168,14 @@ public:
sd(-1), sd(-1),
lock_fd(0), lock_fd(0),
paused(false), paused(false),
step(0) step(0),
maxfps(DEFAULT_MAXFPS),
base_fps(0.0),
effective_fps(0.0),
actual_fps(0.0),
frame_count(0),
last_frame_count(0),
frame_mod(1)
{ {
memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_sock_path, 0, sizeof(loc_sock_path));
memset(&loc_addr, 0, sizeof(loc_addr)); memset(&loc_addr, 0, sizeof(loc_addr));
@ -172,10 +183,6 @@ public:
memset(&rem_addr, 0, sizeof(rem_addr)); memset(&rem_addr, 0, sizeof(rem_addr));
memset(&sock_path_lock, 0, sizeof(sock_path_lock)); memset(&sock_path_lock, 0, sizeof(sock_path_lock));
base_fps = 0.0;
effective_fps = 0.0;
frame_mod = 1;
vid_stream = nullptr; vid_stream = nullptr;
msg = { 0, { 0 } }; msg = { 0, { 0 } };
} }

View File

@ -23,12 +23,6 @@
#include <chrono> #include <chrono>
#include <sys/time.h> #include <sys/time.h>
inline struct timeval tvNow() {
timeval t = {};
gettimeofday(&t, nullptr);
return t;
}
typedef std::chrono::microseconds Microseconds; typedef std::chrono::microseconds Microseconds;
typedef std::chrono::milliseconds Milliseconds; typedef std::chrono::milliseconds Milliseconds;
typedef std::chrono::seconds Seconds; typedef std::chrono::seconds Seconds;

View File

@ -21,6 +21,7 @@
#include "zm_crypt.h" #include "zm_crypt.h"
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_time.h"
#include "zm_utils.h" #include "zm_utils.h"
#include <cstring> #include <cstring>
@ -84,15 +85,7 @@ bool User::canAccess(int monitor_id) {
// Function to load a user from username and password // Function to load a user from username and password
// Please note that in auth relay mode = none, password is NULL // Please note that in auth relay mode = none, password is NULL
User *zmLoadUser(const char *username, const char *password) { User *zmLoadUser(const char *username, const char *password) {
int username_length = strlen(username); std::string escaped_username = zmDbEscapeString(username);
// According to docs, size of safer_whatever must be 2*length+1
// due to unicode conversions + null terminator.
std::string escaped_username((username_length * 2) + 1, '\0');
size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped_username[0], username, username_length);
escaped_username.resize(escaped_len);
std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`," std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`,"
" `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0,"
@ -100,7 +93,7 @@ User *zmLoadUser(const char *username, const char *password) {
" FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1",
escaped_username.c_str()); escaped_username.c_str());
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) if (!result)
return nullptr; return nullptr;
@ -151,7 +144,7 @@ User *zmLoadTokenUser(const std::string &jwt_token_str, bool use_remote_addr) {
" `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`"
" FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str());
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) if (!result)
return nullptr; return nullptr;
@ -194,7 +187,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) {
" `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0,"
" `MonitorIds` FROM `Users` WHERE `Enabled` = 1"; " `MonitorIds` FROM `Users` WHERE `Enabled` = 1";
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) if (!result)
return nullptr; return nullptr;
@ -205,24 +198,27 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) {
return nullptr; return nullptr;
} }
// getting the time is expensive, so only do it once. SystemTimePoint now = std::chrono::system_clock::now();
time_t now = time(nullptr); Hours hours = Hours(config.auth_hash_ttl);
unsigned int hours = config.auth_hash_ttl;
if (!hours) { if (hours == Hours(0)) {
Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2.");
hours = 2; hours = Hours(2);
} else { } else {
Debug(1, "AUTH_HASH_TTL is %d, time is %" PRIi64, hours, static_cast<int64>(now)); Debug(1, "AUTH_HASH_TTL is %" PRIi64 " h, time is %" PRIi64 " s",
static_cast<int64>(Hours(hours).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()));
} }
while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { while (MYSQL_ROW dbrow = mysql_fetch_row(result)) {
const char *username = dbrow[1]; const char *username = dbrow[1];
const char *password = dbrow[2]; const char *password = dbrow[2];
time_t our_now = now; SystemTimePoint our_now = now;
tm now_tm = {}; tm now_tm = {};
for (unsigned int i = 0; i < hours; i++, our_now -= 3600) { for (Hours i = Hours(0); i < hours; i++, our_now -= Hours(1)) {
localtime_r(&our_now, &now_tm); time_t our_now_t = std::chrono::system_clock::to_time_t(our_now);
localtime_r(&our_now_t, &now_tm);
std::string auth_key = stringtf("%s%s%s%s%d%d%d%d", std::string auth_key = stringtf("%s%s%s%s%d%d%d%d",
config.auth_hash_secret, config.auth_hash_secret,

View File

@ -22,6 +22,7 @@
#include "zm_config.h" #include "zm_config.h"
#include "zm_logger.h" #include "zm_logger.h"
#include <array> #include <array>
#include <cstdarg>
#include <cstring> #include <cstring>
#include <fcntl.h> /* Definition of AT_* constants */ #include <fcntl.h> /* Definition of AT_* constants */
#include <sstream> #include <sstream>
@ -116,6 +117,26 @@ std::string Join(const StringVector &values, const std::string &delim) {
return ss.str(); return ss.str();
} }
std::string stringtf(const char* format, ...) {
va_list args;
va_start(args, format);
va_list args2;
va_copy(args2, args);
int size = vsnprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'
va_end(args);
if (size <= 0) {
throw std::runtime_error("Error during formatting.");
}
std::unique_ptr<char[]> buf(new char[size]);
vsnprintf(buf.get(), size, format, args2);
va_end(args2);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
std::string ByteArrayToHexString(nonstd::span<const uint8> bytes) { std::string ByteArrayToHexString(nonstd::span<const uint8> bytes) {
static constexpr char lowercase_table[] = "0123456789abcdef"; static constexpr char lowercase_table[] = "0123456789abcdef";
std::string buf; std::string buf;

View File

@ -61,16 +61,8 @@ inline bool StartsWith(const std::string &haystack, const std::string &needle) {
return (haystack.substr(0, needle.length()) == needle); return (haystack.substr(0, needle.length()) == needle);
} }
template<typename... Args> __attribute__((format(printf, 1, 2)))
std::string stringtf(const std::string &format, Args... args) { std::string stringtf(const char* format, ...);
int size = snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
if (size <= 0) {
throw std::runtime_error("Error during formatting.");
}
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), size, format.c_str(), args...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
std::string ByteArrayToHexString(nonstd::span<const uint8> bytes); std::string ByteArrayToHexString(nonstd::span<const uint8> bytes);

View File

@ -22,6 +22,7 @@
#include "zm_logger.h" #include "zm_logger.h"
#include "zm_monitor.h" #include "zm_monitor.h"
#include "zm_time.h"
extern "C" { extern "C" {
#include <libavutil/time.h> #include <libavutil/time.h>
@ -92,7 +93,7 @@ VideoStore::VideoStore(
converted_in_samples(nullptr), converted_in_samples(nullptr),
filename(filename_in), filename(filename_in),
format(format_in), format(format_in),
video_first_pts(0), /* starting pts of first in frame/packet */ video_first_pts(0),
video_first_dts(0), video_first_dts(0),
audio_first_pts(0), audio_first_pts(0),
audio_first_dts(0), audio_first_dts(0),
@ -989,25 +990,25 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr<ZMPacket> &zm_packet
//zm_packet->out_frame->key_frame = zm_packet->keyframe; //zm_packet->out_frame->key_frame = zm_packet->keyframe;
frame->pkt_duration = 0; frame->pkt_duration = 0;
int64_t in_pts = zm_packet->timestamp.tv_sec * (uint64_t)1000000 + zm_packet->timestamp.tv_usec;
if (!video_first_pts) { if (!video_first_pts) {
video_first_pts = in_pts; video_first_pts = static_cast<int64>(std::chrono::duration_cast<Microseconds>(zm_packet->timestamp.time_since_epoch()).count());
Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%" PRIi64 ") usecs(%" PRIi64 ")", Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%.2f)",
video_first_pts, video_first_pts,
static_cast<int64>(zm_packet->timestamp.tv_sec), FPSeconds(zm_packet->timestamp.time_since_epoch()).count());
static_cast<int64>(zm_packet->timestamp.tv_usec));
frame->pts = 0; frame->pts = 0;
} else { } else {
uint64_t useconds = in_pts - video_first_pts;
frame->pts = av_rescale_q(useconds, AV_TIME_BASE_Q, video_out_ctx->time_base); Microseconds useconds = std::chrono::duration_cast<Microseconds>(
zm_packet->timestamp - SystemTimePoint(Microseconds(video_first_pts)));
frame->pts = av_rescale_q(useconds.count(), AV_TIME_BASE_Q, video_out_ctx->time_base);
Debug(2, Debug(2,
"Setting pts for frame(%d) to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - secs(%" PRIi64 ") usecs(%" PRIi64 ") @ %d/%d", "Setting pts for frame(%d) to (%" PRId64 ") from (zm_packet->timestamp(%" PRIi64 " - first %" PRId64 " us %" PRId64 " ) @ %d/%d",
frame_count, frame_count,
frame->pts, frame->pts,
static_cast<int64>(std::chrono::duration_cast<Microseconds>(zm_packet->timestamp.time_since_epoch()).count()),
video_first_pts, video_first_pts,
useconds, static_cast<int64>(std::chrono::duration_cast<Microseconds>(useconds).count()),
static_cast<int64>(zm_packet->timestamp.tv_sec),
static_cast<int64>(zm_packet->timestamp.tv_usec),
video_out_ctx->time_base.num, video_out_ctx->time_base.num,
video_out_ctx->time_base.den); video_out_ctx->time_base.den);
} }

View File

@ -76,7 +76,7 @@ class VideoStore {
const char *format; const char *format;
// These are for in // These are for in
int64_t video_first_pts; int64_t video_first_pts; /* starting pts of first in frame/packet */
int64_t video_first_dts; int64_t video_first_dts;
int64_t audio_first_pts; int64_t audio_first_pts;
int64_t audio_first_dts; int64_t audio_first_dts;

View File

@ -104,7 +104,8 @@ void Zone::Setup(
diag_path = stringtf("%s/diag-%d-poly.jpg", diag_path = stringtf("%s/diag-%d-poly.jpg",
monitor->getStorage()->Path(), id); monitor->getStorage()->Path(), id);
} }
pg_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo);
pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
} }
} // end Zone::Setup } // end Zone::Setup
@ -116,8 +117,7 @@ Zone::~Zone() {
} }
void Zone::RecordStats(const Event *event) { void Zone::RecordStats(const Event *event) {
static char sql[ZM_SQL_MED_BUFSIZ]; std::string sql = stringtf(
snprintf(sql, sizeof(sql),
"INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, " "INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, "
"PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, " "PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, "
"Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, " "Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, "
@ -233,8 +233,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
} */ } */
std_alarmedpixels(diff_image, pg_image, &stats.alarm_pixels_, &pixel_diff_count); std_alarmedpixels(diff_image, pg_image, &stats.alarm_pixels_, &pixel_diff_count);
if (config.record_diag_images) if (config.record_diag_images) {
diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
}
if (pixel_diff_count && stats.alarm_pixels_) if (pixel_diff_count && stats.alarm_pixels_)
stats.pixel_diff_ = pixel_diff_count/stats.alarm_pixels_; stats.pixel_diff_ = pixel_diff_count/stats.alarm_pixels_;
@ -317,8 +318,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
stats.alarm_filter_pixels_ = stats.alarm_pixels_; stats.alarm_filter_pixels_ = stats.alarm_pixels_;
} }
if (config.record_diag_images) if (config.record_diag_images) {
diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
}
Debug(5, "Got %d filtered pixels, need %d -> %d", Debug(5, "Got %d filtered pixels, need %d -> %d",
stats.alarm_filter_pixels_, min_filter_pixels, max_filter_pixels); stats.alarm_filter_pixels_, min_filter_pixels, max_filter_pixels);
@ -541,8 +543,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
} }
} }
if (config.record_diag_images) if (config.record_diag_images) {
diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
}
if (!stats.alarm_blobs_) { if (!stats.alarm_blobs_) {
stats.score_ = 0; stats.score_ = 0;
@ -593,8 +596,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
} // end if bs_count } // end if bs_count
} // end for i < WHITE } // end for i < WHITE
if (config.record_diag_images) if (config.record_diag_images) {
diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
}
Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d",
stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs);
@ -830,7 +834,7 @@ std::vector<Zone> Zone::Load(Monitor *monitor) {
"OverloadFrames,ExtendAlarmFrames" "OverloadFrames,ExtendAlarmFrames"
" FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id()); " FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id());
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) { if (!result) {
return {}; return {};
} }

View File

@ -64,8 +64,6 @@ possible, this should run at more or less constant speed.
#include "zm_time.h" #include "zm_time.h"
#include "zm_utils.h" #include "zm_utils.h"
#include <getopt.h> #include <getopt.h>
#include <iostream>
#include <unistd.h>
void Usage() { void Usage() {
fprintf(stderr, "zmc -d <device_path> or -r <proto> -H <host> -P <port> -p <path> or -f <file_path> or -m <monitor_id>\n"); fprintf(stderr, "zmc -d <device_path> or -r <proto> -H <host> -P <port> -p <path> or -f <file_path> or -m <monitor_id>\n");
@ -192,11 +190,11 @@ int main(int argc, char *argv[]) {
HwCapsDetect(); HwCapsDetect();
std::vector<std::shared_ptr<Monitor>> monitors; std::vector<std::shared_ptr<Monitor>> monitors;
#if ZM_HAS_V4L #if ZM_HAS_V4L2
if ( device[0] ) { if ( device[0] ) {
monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE); monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE);
} else } else
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
if ( host[0] ) { if ( host[0] ) {
if ( !port ) if ( !port )
port = "80"; port = "80";
@ -214,7 +212,7 @@ int main(int argc, char *argv[]) {
Error("No monitors found"); Error("No monitors found");
exit(-1); exit(-1);
} else { } else {
Debug(2, "%zu monitors loaded", monitors.size()); Debug(2, "%zu monitors loaded", monitors.size());
} }
Info("Starting Capture version %s", ZM_VERSION); Info("Starting Capture version %s", ZM_VERSION);
@ -234,7 +232,6 @@ int main(int argc, char *argv[]) {
while (!zm_terminate) { while (!zm_terminate) {
result = 0; result = 0;
static char sql[ZM_SQL_SML_BUFSIZ];
for (const std::shared_ptr<Monitor> &monitor : monitors) { for (const std::shared_ptr<Monitor> &monitor : monitors) {
monitor->LoadCamera(); monitor->LoadCamera();
@ -242,52 +239,51 @@ int main(int argc, char *argv[]) {
if (!monitor->connect()) { if (!monitor->connect()) {
Warning("Couldn't connect to monitor %d", monitor->Id()); Warning("Couldn't connect to monitor %d", monitor->Id());
} }
time_t now = (time_t)time(nullptr); SystemTimePoint now = std::chrono::system_clock::now();
monitor->setStartupTime(now); monitor->SetStartupTime(now);
monitor->setHeartbeatTime(now); monitor->SetHeartbeatTime(now);
snprintf(sql, sizeof(sql), std::string sql = stringtf(
"INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)" "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)"
" VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", " VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0",
monitor->Id()); monitor->Id());
zmDbDo(sql); zmDbDo(sql);
int sleep_time = 0; Seconds sleep_time = Seconds(0);
while (monitor->PrimeCapture() <= 0) { while (monitor->PrimeCapture() <= 0) {
if (prime_capture_log_count % 60) { if (prime_capture_log_count % 60) {
logPrintf(Logger::ERROR+monitor->Importance(), logPrintf(Logger::ERROR + monitor->Importance(),
"Failed to prime capture of initial monitor"); "Failed to prime capture of initial monitor");
} else { } else {
Debug(1, "Failed to prime capture of initial monitor"); Debug(1, "Failed to prime capture of initial monitor");
} }
prime_capture_log_count ++;
if (zm_terminate) break;
if (sleep_time < 60) sleep_time++;
sleep(sleep_time);
}
if (zm_terminate) break;
snprintf(sql, sizeof(sql), prime_capture_log_count++;
if (zm_terminate) {
break;
}
if (sleep_time < Seconds(60)) {
sleep_time++;
}
std::this_thread::sleep_for(sleep_time);
}
if (zm_terminate){
break;
}
sql = stringtf(
"INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'", "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'",
monitor->Id()); monitor->Id());
zmDbDo(sql); zmDbDo(sql);
} // end foreach monitor } // end foreach monitor
if (zm_terminate) break;
int *capture_delays = new int[monitors.size()]; if (zm_terminate){
int *alarm_capture_delays = new int[monitors.size()]; break;
struct timeval * last_capture_times = new struct timeval[monitors.size()];
for (size_t i = 0; i < monitors.size(); i++) {
last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0;
capture_delays[i] = monitors[i]->GetCaptureDelay();
alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay();
Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)",
capture_delays[i], alarm_capture_delays[i]);
} }
timeval now; std::vector<SystemTimePoint> last_capture_times = std::vector<SystemTimePoint>(monitors.size());
int sleep_time = 0; Microseconds sleep_time = Microseconds(0);
while (!zm_terminate) { while (!zm_terminate) {
//sigprocmask(SIG_BLOCK, &block_set, 0); //sigprocmask(SIG_BLOCK, &block_set, 0);
@ -314,30 +310,28 @@ int main(int argc, char *argv[]) {
} }
// capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate.
int delay = (monitors[i]->GetState() == Monitor::ALARM) ? alarm_capture_delays[i] : capture_delays[i]; Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay()
if (delay) { : monitors[i]->GetCaptureDelay();
gettimeofday(&now, nullptr); if (delay != Seconds(0)) {
if (last_capture_times[i].tv_sec) { SystemTimePoint now = std::chrono::system_clock::now();
Microseconds delta_time = zm::chrono::duration_cast<Microseconds>(now) if (last_capture_times[i].time_since_epoch() != Seconds(0)) {
- zm::chrono::duration_cast<Microseconds>(last_capture_times[i]); Microseconds delta_time = std::chrono::duration_cast<Microseconds>(now - last_capture_times[i]);
// You have to add back in the previous sleep time // You have to add back in the previous sleep time
sleep_time = delay - (delta_time.count() - sleep_time); sleep_time = delay - (delta_time - sleep_time);
Debug(4, Debug(4,
"Sleep time is %d from now: %" PRIi64 ".%" PRIi64" last: %" PRIi64 ".% " PRIi64 " delta % " PRIi64 " delay: %d", "Sleep time is %" PRIi64 " from now: %.2f s last: %.2f s delta % " PRIi64 " us delay: %" PRIi64 " us",
sleep_time, static_cast<int64>(Microseconds(sleep_time).count()),
static_cast<int64>(now.tv_sec), FPSeconds(now.time_since_epoch()).count(),
static_cast<int64>(now.tv_usec), FPSeconds(last_capture_times[i].time_since_epoch()).count(),
static_cast<int64>(last_capture_times[i].tv_sec),
static_cast<int64>(last_capture_times[i].tv_usec),
static_cast<int64>(delta_time.count()), static_cast<int64>(delta_time.count()),
delay); static_cast<int64>(Microseconds(delay).count()));
if (sleep_time > 0) { if (sleep_time > Seconds(0)) {
Debug(4, "usleeping (%d)", sleep_time); std::this_thread::sleep_for(sleep_time);
usleep(sleep_time);
} }
} // end if has a last_capture time } // end if has a last_capture time
last_capture_times[i] = now; last_capture_times[i] = now;
} // end if delay } // end if delay
} // end foreach n_monitors } // end foreach n_monitors
@ -348,30 +342,25 @@ int main(int argc, char *argv[]) {
} }
} // end while ! zm_terminate and connected } // end while ! zm_terminate and connected
for (size_t i = 0; i < monitors.size(); i++) { for (std::shared_ptr<Monitor> & monitor : monitors) {
monitors[i]->Close(); monitor->Close();
monitors[i]->disconnect(); monitor->disconnect();
} }
delete [] alarm_capture_delays;
delete [] capture_delays;
delete [] last_capture_times;
if (zm_reload) { if (zm_reload) {
for (std::shared_ptr<Monitor> &monitor : monitors) { for (std::shared_ptr<Monitor> &monitor : monitors) {
monitor->Reload(); monitor->Reload();
} }
logTerm(); logTerm();
logInit(log_id_string); logInit(log_id_string);
zm_reload = false; zm_reload = false;
} // end if zm_reload } // end if zm_reload
} // end while ! zm_terminate outer connection loop } // end while ! zm_terminate outer connection loop
for (std::shared_ptr<Monitor> &monitor : monitors) { for (std::shared_ptr<Monitor> &monitor : monitors) {
static char sql[ZM_SQL_SML_BUFSIZ]; std::string sql = stringtf(
snprintf(sql, sizeof(sql), "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'",
"INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'",
monitor->Id()); monitor->Id());
zmDbDo(sql); zmDbDo(sql);
} }
@ -382,5 +371,5 @@ int main(int argc, char *argv[]) {
dbQueue.stop(); dbQueue.stop();
zmDbClose(); zmDbClose();
return zm_terminate ? 0 : result; return zm_terminate ? 0 : result;
} }

View File

@ -241,7 +241,7 @@ int main(int argc, const char *argv[], char **envp) {
} }
fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION);
time_t now = time(nullptr); time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
char date_string[64]; char date_string[64];
tm now_tm = {}; tm now_tm = {};
strftime(date_string, sizeof(date_string)-1, strftime(date_string, sizeof(date_string)-1,
@ -265,7 +265,7 @@ int main(int argc, const char *argv[], char **envp) {
stream.setStreamQueue(connkey); stream.setStreamQueue(connkey);
stream.setStreamBuffer(playback_buffer); stream.setStreamBuffer(playback_buffer);
if ( !stream.setStreamStart(monitor_id) ) { if ( !stream.setStreamStart(monitor_id) ) {
Error("Unable set start stream for monitor %d", monitor_id); fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout);
stream.sendTextFrame("Unable to connect to monitor"); stream.sendTextFrame("Unable to connect to monitor");
logTerm(); logTerm();
zmDbClose(); zmDbClose();

View File

@ -197,6 +197,7 @@ bool ValidateAccess(User *user, int mon_id, int function) {
void exit_zmu(int exit_code) { void exit_zmu(int exit_code) {
logTerm(); logTerm();
dbQueue.stop();
zmDbClose(); zmDbClose();
exit(exit_code); exit(exit_code);
@ -248,7 +249,7 @@ int main(int argc, char *argv[]) {
{nullptr, 0, nullptr, 0} {nullptr, 0, nullptr, 0}
}; };
const char *device = nullptr; std::string device;
int mon_id = 0; int mon_id = 0;
bool verbose = false; bool verbose = false;
int function = ZMU_BOGUS; int function = ZMU_BOGUS;
@ -256,28 +257,35 @@ int main(int argc, char *argv[]) {
int image_idx = -1; int image_idx = -1;
int scale = -1; int scale = -1;
int brightness = -1; int brightness = -1;
bool have_brightness = false;
int contrast = -1; int contrast = -1;
bool have_contrast = false;
int hue = -1; int hue = -1;
bool have_hue = false;
int colour = -1; int colour = -1;
bool have_colour = false;
char *zoneString = nullptr; char *zoneString = nullptr;
char *username = nullptr; char *username = nullptr;
char *password = nullptr; char *password = nullptr;
char *auth = nullptr; char *auth = nullptr;
std::string jwt_token_str = ""; std::string jwt_token_str = "";
#if ZM_HAS_V4L #if ZM_HAS_V4L2
int v4lVersion = 2; int v4lVersion = 2;
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
while (1) { while (1) {
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index);
if ( c == -1 ) { if (c == -1) {
break; break;
} }
switch (c) { switch (c) {
case 'd': case 'd':
if ( optarg ) if (optarg)
device = optarg; device = optarg;
break; break;
case 'm': case 'm':
@ -291,7 +299,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'i': case 'i':
function |= ZMU_IMAGE; function |= ZMU_IMAGE;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'S': case 'S':
@ -299,7 +307,7 @@ int main(int argc, char *argv[]) {
break; break;
case 't': case 't':
function |= ZMU_TIME; function |= ZMU_TIME;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'R': case 'R':
@ -316,7 +324,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'z': case 'z':
function |= ZMU_ZONES; function |= ZMU_ZONES;
if ( optarg ) if (optarg)
zoneString = optarg; zoneString = optarg;
break; break;
case 'a': case 'a':
@ -348,23 +356,31 @@ int main(int argc, char *argv[]) {
break; break;
case 'B': case 'B':
function |= ZMU_BRIGHTNESS; function |= ZMU_BRIGHTNESS;
if ( optarg ) if (optarg) {
have_brightness = true;
brightness = atoi(optarg); brightness = atoi(optarg);
}
break; break;
case 'C': case 'C':
function |= ZMU_CONTRAST; function |= ZMU_CONTRAST;
if ( optarg ) if (optarg) {
have_contrast = true;
contrast = atoi(optarg); contrast = atoi(optarg);
}
break; break;
case 'H': case 'H':
function |= ZMU_HUE; function |= ZMU_HUE;
if ( optarg ) if (optarg) {
have_hue = true;
hue = atoi(optarg); hue = atoi(optarg);
}
break; break;
case 'O': case 'O':
function |= ZMU_COLOUR; function |= ZMU_COLOUR;
if ( optarg ) if (optarg) {
have_colour = true;
colour = atoi(optarg); colour = atoi(optarg);
}
break; break;
case 'U': case 'U':
username = optarg; username = optarg;
@ -378,11 +394,11 @@ int main(int argc, char *argv[]) {
case 'T': case 'T':
jwt_token_str = std::string(optarg); jwt_token_str = std::string(optarg);
break; break;
#if ZM_HAS_V4L #if ZM_HAS_V4L2
case 'V': case 'V':
v4lVersion = (atoi(optarg)==1)?1:2; v4lVersion = (atoi(optarg)==1)?1:2;
break; break;
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
case 'h': case 'h':
case '?': case '?':
Usage(0); Usage(0);
@ -404,7 +420,7 @@ int main(int argc, char *argv[]) {
Usage(); Usage();
} }
if ( device && !(function&ZMU_QUERY) ) { if ( !device.empty() && !(function&ZMU_QUERY) ) {
fprintf(stderr, "Error, -d option cannot be used with this option\n"); fprintf(stderr, "Error, -d option cannot be used with this option\n");
Usage(); Usage();
} }
@ -498,20 +514,27 @@ int main(int argc, char *argv[]) {
} }
} }
if ( function & ZMU_TIME ) { if ( function & ZMU_TIME ) {
struct timeval timestamp = monitor->GetTimestamp(image_idx); SystemTimePoint timestamp = monitor->GetTimestamp(image_idx);
if ( verbose ) { if (verbose) {
char timestamp_str[64] = "None"; char timestamp_str[64] = "None";
if ( timestamp.tv_sec ) { if (timestamp.time_since_epoch() != Seconds(0)) {
tm tm_info = {}; tm tm_info = {};
strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(&timestamp.tv_sec, &tm_info)); time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp);
strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(&timestamp_t, &tm_info));
}
Seconds ts_sec = std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch());
Microseconds ts_usec = std::chrono::duration_cast<Microseconds>(timestamp.time_since_epoch() - ts_sec);
if (image_idx == -1) {
printf("Time of last image capture: %s.%02d\n", timestamp_str, static_cast<int32>(ts_usec.count()));
}
else {
printf("Time of image %d capture: %s.%02d\n", image_idx, timestamp_str, static_cast<int32>(ts_usec.count()));
} }
if ( image_idx == -1 )
printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000);
else
printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000);
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) {
printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); fputc(separator, stdout);
}
printf("%.2f", FPSeconds(timestamp.time_since_epoch()).count());
have_output = true; have_output = true;
} }
} }
@ -585,13 +608,16 @@ int main(int argc, char *argv[]) {
// Ensure that we are not recording. So the forced alarm is distinct from what was recording before // Ensure that we are not recording. So the forced alarm is distinct from what was recording before
monitor->ForceAlarmOff(); monitor->ForceAlarmOff();
monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web");
int wait = 10*1000*1000; // 10 seconds
while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait) { Microseconds wait_time = Seconds(10);
while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait_time > Seconds(0)) {
// Wait for monitor to notice. // Wait for monitor to notice.
usleep(1000); Microseconds sleep = Microseconds(1);
wait -= 1000; std::this_thread::sleep_for(sleep);
wait_time -= sleep;
} }
if ( monitor->GetState() != Monitor::ALARM and !wait ) {
if (monitor->GetState() != Monitor::ALARM and wait_time == Seconds(0)) {
Error("Monitor failed to respond to forced alarm."); Error("Monitor failed to respond to forced alarm.");
} else { } else {
printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId());
@ -639,60 +665,60 @@ int main(int argc, char *argv[]) {
monitor->DumpSettings(monString, verbose); monitor->DumpSettings(monString, verbose);
printf("%s\n", monString); printf("%s\n", monString);
} }
if ( function & ZMU_BRIGHTNESS ) { if (function & ZMU_BRIGHTNESS) {
if ( verbose ) { if (verbose) {
if ( brightness >= 0 ) if (have_brightness)
printf("New brightness: %d\n", monitor->actionBrightness(brightness)); printf("New brightness: %d\n", monitor->actionBrightness(brightness));
else else
printf("Current brightness: %d\n", monitor->actionBrightness()); printf("Current brightness: %d\n", monitor->actionBrightness());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( brightness >= 0 ) if (have_brightness)
printf("%d", monitor->actionBrightness(brightness)); printf("%d", monitor->actionBrightness(brightness));
else else
printf("%d", monitor->actionBrightness()); printf("%d", monitor->actionBrightness());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_CONTRAST ) { if (function & ZMU_CONTRAST) {
if ( verbose ) { if (verbose) {
if ( contrast >= 0 ) if (have_contrast)
printf("New brightness: %d\n", monitor->actionContrast(contrast)); printf("New contrast: %d\n", monitor->actionContrast(contrast));
else else
printf("Current contrast: %d\n", monitor->actionContrast()); printf("Current contrast: %d\n", monitor->actionContrast());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( contrast >= 0 ) if (have_contrast)
printf("%d", monitor->actionContrast(contrast)); printf("%d", monitor->actionContrast(contrast));
else else
printf("%d", monitor->actionContrast()); printf("%d", monitor->actionContrast());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_HUE ) { if (function & ZMU_HUE) {
if ( verbose ) { if (verbose) {
if ( hue >= 0 ) if (have_hue)
printf("New hue: %d\n", monitor->actionHue(hue)); printf("New hue: %d\n", monitor->actionHue(hue));
else else
printf("Current hue: %d\n", monitor->actionHue()); printf("Current hue: %d\n", monitor->actionHue());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( hue >= 0 ) if (have_hue)
printf("%d", monitor->actionHue(hue)); printf("%d", monitor->actionHue(hue));
else else
printf("%d", monitor->actionHue()); printf("%d", monitor->actionHue());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_COLOUR ) { if (function & ZMU_COLOUR) {
if ( verbose ) { if (verbose) {
if ( colour >= 0 ) if (have_colour)
printf("New colour: %d\n", monitor->actionColour(colour)); printf("New colour: %d\n", monitor->actionColour(colour));
else else
printf("Current colour: %d\n", monitor->actionColour()); printf("Current colour: %d\n", monitor->actionColour());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( colour >= 0 ) if (have_colour)
printf("%d", monitor->actionColour(colour)); printf("%d", monitor->actionColour(colour));
else else
printf("%d", monitor->actionColour()); printf("%d", monitor->actionColour());
@ -700,7 +726,7 @@ int main(int argc, char *argv[]) {
} }
} }
if ( have_output ) { if (have_output) {
printf("\n"); printf("\n");
} }
if ( !function ) { if ( !function ) {
@ -708,15 +734,15 @@ int main(int argc, char *argv[]) {
} }
} else { // non monitor functions } else { // non monitor functions
if ( function & ZMU_QUERY ) { if ( function & ZMU_QUERY ) {
#if ZM_HAS_V4L #if ZM_HAS_V4L2
char vidString[0x10000] = ""; char vidString[0x10000] = "";
bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose); bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose);
printf("%s", vidString); printf("%s", vidString);
exit_zmu(ok ? 0 : -1); exit_zmu(ok ? 0 : -1);
#else // ZM_HAS_V4L #else // ZM_HAS_V4L2
Error("Video4linux is required for device querying"); Error("Video4linux is required for device querying");
exit_zmu(-1); exit_zmu(-1);
#endif // ZM_HAS_V4L #endif // ZM_HAS_V4L2
} }
if ( function & ZMU_LIST ) { if ( function & ZMU_LIST ) {
@ -726,7 +752,7 @@ int main(int argc, char *argv[]) {
} }
sql += " ORDER BY Id ASC"; sql += " ORDER BY Id ASC";
MYSQL_RES *result = zmDbFetch(sql.c_str()); MYSQL_RES *result = zmDbFetch(sql);
if (!result) { if (!result) {
exit_zmu(-1); exit_zmu(-1);
} }
@ -740,13 +766,14 @@ int main(int argc, char *argv[]) {
if ( monitor_function > 1 ) { if ( monitor_function > 1 ) {
std::shared_ptr<Monitor> monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); std::shared_ptr<Monitor> monitor = Monitor::Load(monitor_id, false, Monitor::QUERY);
if ( monitor && monitor->connect() ) { if ( monitor && monitor->connect() ) {
struct timeval tv = monitor->GetTimestamp(); SystemTimePoint timestamp = monitor->GetTimestamp();
printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8" PRIu64 "%8.2f\n",
printf( "%4d%5d%6d%9d%14.2f%6d%6d%8" PRIu64 "%8.2f\n",
monitor->Id(), monitor->Id(),
monitor_function, monitor_function,
monitor->GetState(), monitor->GetState(),
monitor->GetTriggerState(), monitor->GetTriggerState(),
tv.tv_sec, tv.tv_usec/10000, FPSeconds(timestamp.time_since_epoch()).count(),
monitor->GetLastReadIndex(), monitor->GetLastReadIndex(),
monitor->GetLastWriteIndex(), monitor->GetLastWriteIndex(),
monitor->GetLastEventId(), monitor->GetLastEventId(),
@ -754,13 +781,12 @@ int main(int argc, char *argv[]) {
); );
} }
} else { } else {
struct timeval tv = { 0, 0 };
printf("%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", printf("%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n",
mon_id, mon_id,
function, function,
0, 0,
0, 0,
tv.tv_sec, tv.tv_usec/10000, 0l, 0l,
0, 0,
0, 0,
0, 0,

View File

@ -88,7 +88,7 @@ fi;
if [ "$DISTROS" == "" ]; then if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then if [ "$RELEASE" != "" ]; then
DISTROS="bionic,focal,groovy,hirsute" DISTROS="bionic,focal,hirsute,impish"
else else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi; fi;
@ -120,6 +120,11 @@ else
if [ "$BRANCH" == "" ]; then if [ "$BRANCH" == "" ]; then
#REV=$(git rev-list --tags --max-count=1) #REV=$(git rev-list --tags --max-count=1)
BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`;
if [ -z "$BRANCH" ]; then
# This should only happen in CI environments where tag info isn't available
BRANCH=`cat version`
echo "Building branch $BRANCH"
fi
if [ "$BRANCH" == "" ]; then if [ "$BRANCH" == "" ]; then
echo "Unable to determine latest stable branch!" echo "Unable to determine latest stable branch!"
exit 0; exit 0;
@ -224,6 +229,7 @@ rm -rf .git
rm .gitignore rm .gitignore
cd ../ cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi; fi;
@ -237,11 +243,10 @@ IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
fi; fi;
# Generate Changlog # Generate Changlog
if [ "$DISTRO" == "focal" ] || [ "$DISTRO" == "buster" ] || [ "$DISTRO" == "hirsute" ]; then if [ "$DISTRO" == "beowulf" ]; then
cp -Rpd distros/ubuntu2004 debian
elif [ "$DISTRO" == "beowulf" ]
then
cp -Rpd distros/beowulf debian cp -Rpd distros/beowulf debian
else
cp -Rpd distros/ubuntu2004 debian
fi; fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then
@ -291,24 +296,29 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
EOF EOF
fi; fi;
# Leave the .orig so that we don't pollute it when building deps
cd ..
if [ $TYPE == "binary" ]; then if [ $TYPE == "binary" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?" echo "Status: $?"
DEBUILD=debuild DEBUILD=debuild
else else
if [ $TYPE == "local" ]; then if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?" echo "Status: $?"
DEBUILD="debuild -i -us -uc -b" DEBUILD="debuild -i -us -uc -b"
else else
# Source build, don't need build depends. # Source build, don't need build depends.
DEBUILD="debuild -S -sa" DEBUILD="debuild -S -sa"
fi; fi;
fi; fi;
cd $DIRECTORY.orig
if [ "$DEBSIGN_KEYID" != "" ]; then if [ "$DEBSIGN_KEYID" != "" ]; then
DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" DEBUILD="$DEBUILD -k$DEBSIGN_KEYID"
fi fi

Some files were not shown because too many files have changed in this diff Show More