diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..682db9004 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +web/api/lib +web/skins/classic/js/jquery-1.11.3.js +web/skins/classic/js/jquery.js +web/tools/mootools diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..fa4d6b6e5 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +"use strict"; + +module.exports = { + "env": { + "browser": true, + }, + "extends": ["google"], + "rules": { + "brace-style": "off", + "camelcase": "off", + "comma-dangle": "off", + "key-spacing": "off", + "max-len": "off", + "new-cap": ["error", { + capIsNewExceptions: ["Error", "Warning", "Debug", "Polygon_calcArea", "Play", "Stop"], + newIsCapExceptionPattern: "^Asset\.." + }], + "no-array-constructor": "off", + "no-caller": "off", + "no-new-object": "off", + "no-unused-vars": "off", + "no-var": "off", + "object-curly-spacing": "off", + "prefer-rest-params": "off", + "quotes": "off", + "require-jsdoc": "off", + "spaced-comment": "off", + }, +}; diff --git a/.travis.yml b/.travis.yml index 65e7bed95..3afa0f02a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,51 +1,56 @@ language: cpp sudo: required dist: trusty +git: + depth: 9999999 notifications: - irc: "chat.freenode.net#zoneminder-dev" + irc: chat.freenode.net#zoneminder-dev branches: except: - - modern + - modern cache: ccache addons: - sauce_connect: - username: "zoneminder" - access_key: "046ec7c1-c598-4e7e-949a-f86e725d1722" + ssh_known_hosts: zmrepo.zoneminder.com + apt: + sources: + - sourceline: ppa:iconnor/zoneminder + - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 + packages: + - gdebi + - yum-utils + - patch + - git + - curl + - sshfs + - sed env: - global: - - LD_LIBRARY_PATH="/usr/local/lib:/opt/libjpeg-turbo/lib:$LD_LIBRARY_PATH" - - DEB_HOST_GNU_TYPE=$(dpkg-architecture -qDEB_HOST_GNU_TYPE) - - DEB_BUILD_GNU_TYPE=$(dpkg-architecture -qDEB_BUILD_GNU_TYPE) - - CFLAGS="-DZM_FFMPEG_CVS -DHAVE_LIBCRYPTO -I/usr/local/include" - - CXXFLAGS="$CFLAGS" matrix: - - ZM_BUILDMETHOD=cmake + - OS=el DIST=6 + - OS=el DIST=7 + - OS=fedora DIST=24 + - OS=fedora DIST=25 + - OS=ubuntu DIST=trusty + - OS=ubuntu DIST=xenial + - OS=ubuntu DIST=trusty ARCH=i386 + - OS=ubuntu DIST=xenial ARCH=i386 compiler: - - gcc - - clang +- gcc services: - - mysql -before_install: - - sudo apt-get update -qq - - sudo apt-get install -y libpolkit-gobject-1-dev zlib1g-dev apache2 php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf cmake libjpeg-turbo8-dev apache2-mpm-prefork libapache2-mod-php5 php5-cli libtheora-dev libvorbis-dev libvpx-dev libx264-dev libvlccore-dev libvlc-dev -install: - - git clone -b n3.0 --depth=1 git://source.ffmpeg.org/ffmpeg.git - - cd ffmpeg - - ./configure --enable-shared --enable-swscale --enable-gpl --enable-libx264 --enable-libvpx --enable-libvorbis --enable-libtheora - - make -j `grep processor /proc/cpuinfo|wc -l` - - sudo make install - - sudo make install-libs -before_script: - - cd $TRAVIS_BUILD_DIR - - mysql -uroot -e "CREATE DATABASE IF NOT EXISTS zm" - - mysql -uroot -e "GRANT ALL ON zm.* TO 'zmuser'@'localhost' IDENTIFIED BY 'zmpass'"; - - mysql -uroot -e "FLUSH PRIVILEGES" +- mysql +- docker script: - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then cmake -DZM_ONVIF=OFF -DCMAKE_INSTALL_PREFIX="/usr"; fi - - make - - sudo make install - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then sudo ./zmlinkcontent.sh; fi - - mysql -uzmuser -pzmpass < db/zm_create.sql - - mysql -uzmuser -pzmpass zm < db/test.monitor.sql - - sudo zmpkg.pl start - - sudo zmfilter.pl -f purgewhenfull +- utils/packpack/startpackpack.sh + +before_deploy: +- openssl aes-256-cbc -K $encrypted_62a62750aa73_key -iv $encrypted_62a62750aa73_iv -in ./utils/packpack/deploy_rsa.enc -out /tmp/deploy_rsa -d +- eval "$(ssh-agent -s)" +- chmod 600 /tmp/deploy_rsa +- ssh-add /tmp/deploy_rsa + +deploy: + provider: script + skip_cleanup: true + script: utils/packpack/rsync_xfer.sh + on: + branch: master + diff --git a/CMakeLists.txt b/CMakeLists.txt index baba4218d..9737e4d15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,24 @@ set(CMAKE_CXX_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +# GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. +# Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, +# because passing -fmpu=neon is unsafe to processors that don't support neon +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + EXEC_PROGRAM(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) + IF(neonresult EQUAL 0) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") + ELSE(neonresult EQUAL 0) + add_definitions(-DZM_STRIP_NEON=1) + message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") + ENDIF(neonresult EQUAL 0) + ENDIF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) +ENDIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + # Modules that we need: include (GNUInstallDirs) include (CheckIncludeFile) @@ -85,7 +103,8 @@ mark_as_advanced( ZM_PERL_MM_PARMS ZM_PERL_SEARCH_PATH ZM_TARGET_DISTRO - ZM_CONFIG_DIR) + ZM_CONFIG_DIR + ZM_SYSTEMD) set(ZM_RUNDIR "/var/run/zm" CACHE PATH "Location of transient process files, default: /var/run/zm") @@ -148,6 +167,8 @@ set(ZM_PERL_SEARCH_PATH "" CACHE PATH installed outside Perl's default search path.") set(ZM_TARGET_DISTRO "" CACHE STRING "Build ZoneMinder for a specific distribution. Currently, valid names are: fc24, fc25, el6, el7, OS13, FreeBSD") +set(ZM_SYSTEMD "OFF" CACHE BOOL + "Set to ON to force building ZM with systemd support. default: OFF") # Reassign some variables if a target distro has been specified if((ZM_TARGET_DISTRO STREQUAL "fc24") OR (ZM_TARGET_DISTRO STREQUAL "fc25")) @@ -205,6 +226,11 @@ include_directories("${CMAKE_BINARY_DIR}") # This is required to enable searching in lib64 (if exists), do not change set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) +# Set the systemd flag if systemd is autodetected or ZM_SYSTEMD has been set +if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + set(WITH_SYSTEMD 1) +endif(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + # System checks check_include_file("libv4l1-videodev.h" HAVE_LIBV4L1_VIDEODEV_H) if(NOT HAVE_LIBV4L1_VIDEODEV_H) @@ -386,13 +412,13 @@ find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) set(HAVE_LIBMYSQLCLIENT 1) list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") - find_path(MYSQLCLIENT_INCLUDE_DIR mysql/mysql.h) + find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) if(MYSQLCLIENT_INCLUDE_DIR) include_directories("${MYSQLCLIENT_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") endif(MYSQLCLIENT_INCLUDE_DIR) mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) - check_include_file("mysql/mysql.h" HAVE_MYSQL_H) + check_include_file("mysql.h" HAVE_MYSQL_H) if(NOT HAVE_MYSQL_H) message(FATAL_ERROR "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") @@ -402,6 +428,59 @@ else(MYSQLCLIENT_LIBRARIES) "ZoneMinder requires mysqlclient but it was not found on your system") endif(MYSQLCLIENT_LIBRARIES) +# x264 (using find_library and find_path) +find_library(X264_LIBRARIES x264) +if(X264_LIBRARIES) + set(HAVE_LIBX264 1) + list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}") + find_path(X264_INCLUDE_DIR x264.h) + if(X264_INCLUDE_DIR) + include_directories("${X264_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}") + endif(X264_INCLUDE_DIR) + mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR) + check_include_files("stdint.h;x264.h" HAVE_X264_H) + set(optlibsfound "${optlibsfound} x264") +else(X264_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} x264") +endif(X264_LIBRARIES) + +# mp4v2 (using find_library and find_path) +find_library(MP4V2_LIBRARIES mp4v2) +if(MP4V2_LIBRARIES) + set(HAVE_LIBMP4V2 1) + list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}") + + # mp4v2/mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H) + + # mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2.h" HAVE_MP4V2_H) + + # mp4.h + find_path(MP4V2_INCLUDE_DIR mp4.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4.h" HAVE_MP4_H) + + mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR) + set(optlibsfound "${optlibsfound} mp4v2") +else(MP4V2_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} mp4v2") +endif(MP4V2_LIBRARIES) + set(PATH_FFMPEG "") set(OPT_FFMPEG "no") # Do not check for ffmpeg if ZM_NO_FFMPEG is on @@ -492,6 +571,23 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} SWScale") endif(SWSCALE_LIBRARIES) + # rescale (using find_library and find_path) + find_library(AVRESAMPLE_LIBRARIES avresample) + if(AVRESAMPLE_LIBRARIES) + set(HAVE_LIBAVRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") + find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) + if(AVRESAMPLE_INCLUDE_DIR) + include_directories("${AVRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") + endif(AVRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) + check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) + set(optlibsfound "${optlibsfound} AVResample") + else(AVRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} AVResample") + endif(AVRESAMPLE_LIBRARIES) + # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE NAMES ffmpeg avconv @@ -524,6 +620,13 @@ if(NOT ZM_NO_LIBVLC) endif(LIBVLC_LIBRARIES) endif(NOT ZM_NO_LIBVLC) +find_package(Boost 1.36.0) +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") + list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") +endif() + # *** END OF LIBRARY CHECKS *** # Check for gnutls or crypto @@ -659,12 +762,14 @@ endif(NOT ZM_WEB_GROUP) message(STATUS "Using web user: ${ZM_WEB_USER}") message(STATUS "Using web group: ${ZM_WEB_GROUP}") -# Check for polkit -find_package(Polkit) -if(NOT POLKIT_FOUND) - message(FATAL_ERROR - "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") -endif(NOT POLKIT_FOUND) +if(WITH_SYSTEMD) + # Check for polkit + find_package(Polkit) + if(NOT POLKIT_FOUND) + message(FATAL_ERROR + "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") + endif(NOT POLKIT_FOUND) +endif(WITH_SYSTEMD) # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") @@ -749,3 +854,4 @@ if(CCACHE_FOUND) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif(CCACHE_FOUND) + diff --git a/Dockerfile b/Dockerfile index 837a6d4ce..27b5bc636 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ZoneMinder -FROM ubuntu:trusty -MAINTAINER Kyle Johnson +FROM ubuntu:xenial +MAINTAINER Markos Vakondios # Resynchronize the package index files RUN apt-get update && \ @@ -10,8 +10,8 @@ RUN apt-get update && \ libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libpcre3 \ libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm cmake libjpeg-turbo8-dev \ libjpeg-turbo8 libtheora-dev libvorbis-dev libvpx-dev libx264-dev libmp4v2-dev libav-tools mysql-client \ - apache2 php5 php5-mysql apache2-mpm-prefork libapache2-mod-php5 php5-cli openssh-server \ - mysql-server libvlc-dev libvlc5 libvlccore-dev libvlccore7 vlc-data libcurl4-openssl-dev \ + apache2 php php-mysql libapache2-mod-php php-cli \ + mysql-server libvlc-dev libvlc5 libvlccore-dev libvlccore8 vlc-data libcurl4-openssl-dev \ libavformat-dev libswscale-dev libavutil-dev libavcodec-dev libavfilter-dev \ libavresample-dev libavdevice-dev libpostproc-dev libv4l-dev libtool libnetpbm10-dev \ libmime-lite-perl dh-autoreconf dpatch \ @@ -42,22 +42,12 @@ ADD utils/docker/start.sh /tmp/start.sh # give files in /usr/local/share/zoneminder/ RUN chown -R www-data:www-data /usr/local/share/zoneminder/ -# Creating SSH privilege escalation dir -RUN mkdir /var/run/sshd - # Adding apache virtual hosts file -ADD utils/docker/apache-vhost /etc/apache2/sites-available/000-default.conf +RUN cp misc/apache.conf /etc/apache2/sites-available/000-default.conf ADD utils/docker/phpdate.ini /etc/php5/apache2/conf.d/25-phpdate.ini -# Set the root passwd -RUN echo 'root:root' | chpasswd - -# Add a user we can actually login with -RUN useradd -m -s /bin/bash -G sudo zoneminder -RUN echo 'zoneminder:zoneminder' | chpasswd - -# Expose ssh and http ports -EXPOSE 22 80 +# Expose http port +EXPOSE 80 # Initial database and apache setup: RUN "/ZoneMinder/utils/docker/setup.sh" diff --git a/code_of_conduct.md b/code_of_conduct.md new file mode 100644 index 000000000..d05f4bc06 --- /dev/null +++ b/code_of_conduct.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index e9df807f7..765e15519 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -193,6 +193,7 @@ CREATE TABLE `Events` ( `Length` decimal(10,2) NOT NULL default '0.00', `Frames` int(10) unsigned default NULL, `AlarmFrames` int(10) unsigned default NULL, + `DefaultVideo` VARCHAR( 64 ) NOT NULL, `TotScore` int(10) unsigned NOT NULL default '0', `AvgScore` smallint(5) unsigned default '0', `MaxScore` smallint(5) unsigned default '0', @@ -323,7 +324,7 @@ CREATE TABLE `Monitors` ( `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', - `LinkedMonitors` varchar(255) NOT NULL default '', + `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', @@ -332,26 +333,30 @@ CREATE TABLE `Monitors` ( `V4LCapturesPerFrame` tinyint(3) unsigned, `Protocol` varchar(16) NOT NULL default '', `Method` varchar(16) NOT NULL default '', - `Host` varchar(64) NOT NULL default '', + `Host` varchar(64), `Port` varchar(8) NOT NULL default '', `SubPath` varchar(64) NOT NULL default '', - `Path` varchar(255) NOT NULL default '', + `Path` varchar(255), `Options` varchar(255) not null default '', - `User` varchar(64) NOT NULL default '', - `Pass` varchar(64) NOT NULL default '', + `User` varchar(64), + `Pass` varchar(64), `Width` smallint(5) unsigned NOT NULL default '0', `Height` smallint(5) unsigned NOT NULL default '0', `Colours` tinyint(3) unsigned NOT NULL default '1', `Palette` int(10) unsigned NOT NULL default '0', `Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0', `Deinterlacing` int(10) unsigned NOT NULL default '0', - `RTSPDescribe` tinyint(1) unsigned NOT NULL default '0', + `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , + `VideoWriter` TINYINT NOT NULL DEFAULT '0', + `EncoderParameters` TEXT, + `RecordAudio` TINYINT NOT NULL DEFAULT '0', + `RTSPDescribe` tinyint(1) unsigned, `Brightness` mediumint(7) NOT NULL default '-1', `Contrast` mediumint(7) NOT NULL default '-1', `Hue` mediumint(7) NOT NULL default '-1', `Colour` mediumint(7) NOT NULL default '-1', `EventPrefix` varchar(32) NOT NULL default 'Event-', - `LabelFormat` varchar(64) NOT NULL default '%N - %y/%m/%d %H:%M:%S', + `LabelFormat` varchar(64), `LabelX` smallint(5) unsigned NOT NULL default '0', `LabelY` smallint(5) unsigned NOT NULL default '0', `LabelSize` smallint(5) unsigned NOT NULL DEFAULT '1', @@ -372,14 +377,14 @@ CREATE TABLE `Monitors` ( `RefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `AlarmRefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `Controllable` tinyint(3) unsigned NOT NULL default '0', - `ControlId` int(10) unsigned NOT NULL default '0', + `ControlId` int(10) unsigned, `ControlDevice` varchar(255) default NULL, `ControlAddress` varchar(255) default NULL, `AutoStopTimeout` decimal(5,2) default NULL, `TrackMotion` tinyint(3) unsigned NOT NULL default '0', - `TrackDelay` smallint(5) unsigned NOT NULL default '0', + `TrackDelay` smallint(5) unsigned, `ReturnLocation` tinyint(3) NOT NULL default '-1', - `ReturnDelay` smallint(5) unsigned NOT NULL default '0', + `ReturnDelay` smallint(5) unsigned, `DefaultView` enum('Events','Control') NOT NULL default 'Events', `DefaultRate` smallint(5) unsigned NOT NULL default '100', `DefaultScale` smallint(5) unsigned NOT NULL default '100', @@ -468,7 +473,7 @@ CREATE TABLE `Users` ( `Id` int(10) unsigned NOT NULL auto_increment, `Username` varchar(32) character set latin1 collate latin1_bin NOT NULL default '', `Password` varchar(64) NOT NULL default '', - `Language` varchar(8) NOT NULL default '', + `Language` varchar(8), `Enabled` tinyint(3) unsigned NOT NULL default '1', `Stream` enum('None','View') NOT NULL default 'None', `Events` enum('None','View','Edit') NOT NULL default 'None', @@ -477,8 +482,8 @@ CREATE TABLE `Users` ( `Groups` enum('None','View','Edit') NOT NULL default 'None', `Devices` enum('None','View','Edit') NOT NULL default 'None', `System` enum('None','View','Edit') NOT NULL default 'None', - `MaxBandwidth` varchar(16) NOT NULL default '', - `MonitorIds` tinytext NOT NULL, + `MaxBandwidth` varchar(16), + `MonitorIds` tinytext, PRIMARY KEY (`Id`), UNIQUE KEY `UC_Username` (`Username`) ) ENGINE=@ZM_MYSQL_ENGINE@; diff --git a/db/zm_update-1.30.2.sql b/db/zm_update-1.30.2.sql index 48e9b3c52..50c318f6b 100644 --- a/db/zm_update-1.30.2.sql +++ b/db/zm_update-1.30.2.sql @@ -1,20 +1,5 @@ -- -- This updates a 1.30.1 database to 1.30.2 -- --- Add StateId Column to Events. --- - -SET @s = (SELECT IF( - (SELECT COUNT(*) - FROM INFORMATION_SCHEMA.COLUMNS - WHERE table_name = 'Events' - AND table_schema = DATABASE() - AND column_name = 'StateId' - ) > 0, -"SELECT 'Column StateId exists in Events'", -"ALTER TABLE Events ADD `StateId` int(10) unsigned default NULL AFTER `Notes`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; +ALTER TABLE Users MODIFY MonitorIds TEXT NOT NULL; diff --git a/db/zm_update-1.30.3.sql b/db/zm_update-1.30.3.sql new file mode 100644 index 000000000..d3a0c740d --- /dev/null +++ b/db/zm_update-1.30.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.30.2 database to 1.30.3 +-- +-- No changes required +-- diff --git a/db/zm_update-1.30.4.sql b/db/zm_update-1.30.4.sql new file mode 100644 index 000000000..267562291 --- /dev/null +++ b/db/zm_update-1.30.4.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.30.3 database to 1.30.4 +-- +-- No changes required +-- diff --git a/db/zm_update-1.31.0.sql b/db/zm_update-1.31.0.sql new file mode 100644 index 000000000..06e94c10e --- /dev/null +++ b/db/zm_update-1.31.0.sql @@ -0,0 +1,97 @@ +-- +-- This updates a 1.29.0 database to 1.30.0 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'SaveJPEGs' + ) > 0, +"SELECT 'Column SaveJPEGs exists in Monitors'", +"ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'VideoWriter' + ) > 0, +"SELECT 'Column VideoWriter exists in Monitors'", +"ALTER TABLE `Monitors` ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'EncoderParameters' + ) > 0, +"SELECT 'Column EncoderParameters exists in Monitors'", +"ALTER TABLE `Monitors` ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'DefaultVideo' + ) > 0, +"SELECT 'Column DefaultVideo exists in Events'", +"ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'RecordAudio' + ) > 0, +"SELECT 'Column RecordAudio exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- +-- The following alters various columns to allow NULLs +-- + +ALTER TABLE Monitors MODIFY Host varchar(64); +ALTER TABLE Monitors MODIFY LabelFormat varchar(64); +ALTER TABLE Monitors MODIFY LinkedMonitors varchar(255); +ALTER TABLE Monitors MODIFY Options varchar(255); +ALTER TABLE Monitors MODIFY Protocol varchar(16); +ALTER TABLE Monitors MODIFY User varchar(64); +ALTER TABLE Monitors MODIFY Pass varchar(64); +ALTER TABLE Monitors MODIFY RTSPDescribe tinyint(1) unsigned; +ALTER TABLE Monitors MODIFY ControlId int(10) unsigned; +ALTER TABLE Monitors MODIFY TrackDelay smallint(5) unsigned; +ALTER TABLE Monitors MODIFY ReturnDelay smallint(5) unsigned; +ALTER TABLE Monitors MODIFY EncoderParameters TEXT; +ALTER TABLE Monitors MODIFY Path varchar(255); +ALTER TABLE Monitors MODIFY V4LMultiBuffer tinyint(1) unsigned; + +ALTER TABLE Users MODIFY MonitorIds tinytext; +ALTER TABLE Users MODIFY Language varchar(8); +ALTER TABLE Users MODIFY MaxBandwidth varchar(16); + diff --git a/db/zm_update-1.31.1.sql b/db/zm_update-1.31.1.sql new file mode 100644 index 000000000..18d825091 --- /dev/null +++ b/db/zm_update-1.31.1.sql @@ -0,0 +1,20 @@ +-- +-- This updates a 1.30.0 database to 1.30.1 +-- +-- Add StateId Column to Events. +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'StateId' + ) > 0, +"SELECT 'Column StateId exists in Events'", +"ALTER TABLE Events ADD `StateId` int(10) unsigned default NULL AFTER `Notes`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/distros/debian/apache.conf b/distros/debian/apache.conf index 9a3238807..b0a22c4d8 100644 --- a/distros/debian/apache.conf +++ b/distros/debian/apache.conf @@ -1,8 +1,20 @@ Alias /zm /usr/share/zoneminder/www - - Options -Indexes +FollowSymLinks - - DirectoryIndex index.php - - + + + Options -Indexes +ExecCGI + AllowOverride All + AddHandler fcgid-script .php + FCGIWrapper /usr/bin/php5-cgi + Order allow,deny + Allow from all + + + + + Options -Indexes +FollowSymLinks + + DirectoryIndex index.php + + + diff --git a/distros/debian/control b/distros/debian/control index 9bd3ab88a..af37ee7bc 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -6,14 +6,14 @@ Build-Depends: debhelper (>= 9), cmake , libphp-serialization-perl , libgnutls28-dev | libgnutls-dev , libmysqlclient-dev | libmariadbclient-dev - , libjpeg8-dev + , libjpeg8-dev | libjpeg-dev , libpcre3-dev , libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev + , libavdevice-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , ffmpeg | libav-tools , libnetpbm10-dev - , libavdevice-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev , libgcrypt11-dev, libpolkit-gobject-1-dev @@ -42,11 +42,11 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} , libsys-cpu-perl, libsys-meminfo-perl , libdata-uuid-perl , libpcre3 - , ffmpeg | libav-tools, libavdevice53 + , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 , rsyslog | system-log-daemon , netpbm , libjpeg8 , zip - , libvlccore5 | libvlccore7, libvlc5 + , libvlccore5 | libvlccore7 | libvlccore8, libvlc5 , libpolkit-gobject-1-0, php5-gd Recommends: mysql-server | mariadb-server Description: Video camera security and surveillance solution diff --git a/distros/debian/rules b/distros/debian/rules index 473871a49..48a3bdab0 100755 --- a/distros/debian/rules +++ b/distros/debian/rules @@ -27,9 +27,9 @@ override_dh_auto_configure: override_dh_auto_install: dh_auto_install --buildsystem=cmake install -D -m 0644 debian/apache.conf $(INSTDIR)/etc/zm/apache.conf - rm $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt - rm $(INSTDIR)/usr/share/zoneminder/www/api/.gitignore - rm -r $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/Test + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/.gitignore + rm -rf $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/Test override_dh_auto_test: # do not run tests... diff --git a/distros/fedora b/distros/fedora new file mode 120000 index 000000000..4d0827986 --- /dev/null +++ b/distros/fedora @@ -0,0 +1 @@ +redhat \ No newline at end of file diff --git a/distros/fedora/README.txt b/distros/fedora/README.txt deleted file mode 100644 index 09659129f..000000000 --- a/distros/fedora/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Fedora rpm build files have been merged with redhat. -See /distros/redhat. - diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index f8acef552..63159b1c7 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -51,6 +51,7 @@ file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) # Install the empty folders install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) diff --git a/distros/redhat/nginx/README.Fedora b/distros/redhat/nginx/README.Fedora index 2ae3c2dcb..0a5168231 100644 --- a/distros/redhat/nginx/README.Fedora +++ b/distros/redhat/nginx/README.Fedora @@ -157,7 +157,9 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart nginx and php-fpm then start and zoneminder: + sudo systemctl restart nginx + sudo systemctl restart php-fpm sudo systemctl start zoneminder diff --git a/distros/redhat/readme/README.Fedora b/distros/redhat/readme/README.Fedora index 6e169a039..626d74df1 100644 --- a/distros/redhat/readme/README.Fedora +++ b/distros/redhat/readme/README.Fedora @@ -151,7 +151,8 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart the web server then start zoneminder: + sudo systemctl restart httpd sudo systemctl start zoneminder diff --git a/distros/redhat/readme/README.Redhat6 b/distros/redhat/readme/README.Redhat6 index 2026b94cb..a913f7d76 100644 --- a/distros/redhat/readme/README.Redhat6 +++ b/distros/redhat/readme/README.Redhat6 @@ -150,7 +150,8 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart the web server then start zoneminder: + sudo service httpd restart sudo service zoneminder start diff --git a/distros/redhat/readme/README.Redhat7 b/distros/redhat/readme/README.Redhat7 index 540b1cb8a..fd3b87cc4 100644 --- a/distros/redhat/readme/README.Redhat7 +++ b/distros/redhat/readme/README.Redhat7 @@ -142,7 +142,9 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart the web server then start zoneminder: + sudo systemctl restart httpd sudo systemctl start zoneminder + diff --git a/distros/redhat/readme/README.https b/distros/redhat/readme/README.https index f808432f0..7e4132a4a 100644 --- a/distros/redhat/readme/README.https +++ b/distros/redhat/readme/README.https @@ -23,3 +23,7 @@ here are a couple of considerations you may want to take. directives found in /etc/httpd/conf.d/zoneminder.conf. You should also comment out the HTTP -> HTTPS Rewrite rule. +3. Install a fully signed certificate from letsencrypt. See the Letsencrypt + site for more information. https://letsencrypt.org/ + This service is totally free! + diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index a3bb48865..dad18d307 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -6,9 +6,9 @@ %if "%{zmuid_final}" == "nginx" %global with_nginx 1 -%global wwwconfdir /etc/nginx/default.d +%global wwwconfdir %{_sysconfdir}/nginx/default.d %else -%global wwwconfdir /etc/httpd/conf.d +%global wwwconfdir %{_sysconfdir}/httpd/conf.d %endif %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt @@ -17,6 +17,11 @@ # This will tell zoneminder's cmake process we are building against a known distro %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} +# Fedora >= 25 needs apcu backwards compatibility module +%if 0%{?fedora} >= 25 +%global with_apcu_bc 1 +%endif + # Include files for SysV init or systemd %if 0%{?fedora} >= 15 || 0%{?rhel} >= 7 %global with_init_systemd 1 @@ -24,19 +29,12 @@ %global with_init_sysv 1 %endif -# php-mysql deprecated in f25 -%if 0%{?fedora} >= 25 -%global with_php_mysqlnd 1 -%else -%global with_php_mysql 1 -%endif - %global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora} %global _hardened_build 1 Name: zoneminder -Version: 1.30.1 -Release: 2%{?dist} +Version: 1.30.4 +Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons # jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ @@ -46,13 +44,15 @@ Group: System Environment/Daemons License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ -Source0: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz +Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz %{?with_init_systemd:BuildRequires: systemd-devel} %{?with_init_systemd:BuildRequires: mariadb-devel} %{?with_init_systemd:BuildRequires: perl-podlators} +%{?with_init_systemd:BuildRequires: polkit-devel} %{?with_init_sysv:BuildRequires: mysql-devel} +%{?el6:BuildRequires: epel-rpm-macros} BuildRequires: cmake >= 2.8.7 BuildRequires: gnutls-devel BuildRequires: bzip2-devel @@ -82,17 +82,17 @@ BuildRequires: vlc-devel BuildRequires: libcurl-devel BuildRequires: libv4l-devel BuildRequires: ffmpeg-devel -BuildRequires: polkit-devel %{?with_nginx:Requires: nginx} %{?with_nginx:Requires: fcgiwrap} %{?with_nginx:Requires: php-fpm} -%{!?with_nginx:Requires: httpd php} +%{!?with_nginx:Requires: httpd} %{!?with_nginx:Requires: php} -%{?with_php_mysqlnd:Requires: php-mysqlnd} -%{?with_php_mysql:Requires: php-mysql} +Requires: php-mysqli Requires: php-common Requires: php-gd +Requires: php-pecl-apcu +%{?with_apcu_bc:Requires: php-pecl-apcu-bc} Requires: cambozola Requires: net-tools Requires: psmisc @@ -137,10 +137,9 @@ designed to support as many cameras as you can attach to your computer without too much degradation of performance. %prep -%autosetup -%autosetup -a 1 -rmdir ./web/api/app/Plugin/Crud -mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud +%autosetup -p 1 -a 1 -n ZoneMinder-%{version} +%{__rm} -rf ./web/api/app/Plugin/Crud +%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud # Change the following default values ./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms @@ -281,9 +280,9 @@ rm -rf %{_docdir}/%{name}-%{version} %files %license COPYING %doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc -%config(noreplace) %attr(640,root,%{zmgid_final}) /etc/zm/zm.conf +%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf %config(noreplace) %attr(644,root,root) %{wwwconfdir}/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder +%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder %if 0%{?with_nginx} %config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf @@ -292,6 +291,8 @@ rm -rf %{_docdir}/%{name}-%{version} %if 0%{?with_init_systemd} %{_tmpfilesdir}/zoneminder.conf %{_unitdir}/zoneminder.service +%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy +%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules %endif %if 0%{?with_init_sysv} @@ -304,7 +305,6 @@ rm -rf %{_docdir}/%{name}-%{version} %{_bindir}/zmc %{_bindir}/zmcontrol.pl %{_bindir}/zmdc.pl -%{_bindir}/zmf %{_bindir}/zmfilter.pl %{_bindir}/zmpkg.pl %{_bindir}/zmtrack.pl @@ -329,9 +329,6 @@ rm -rf %{_docdir}/%{name}-%{version} %{_libexecdir}/zoneminder/ %{_datadir}/zoneminder/ -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/images @@ -340,9 +337,23 @@ rm -rf %{_docdir}/%{name}-%{version} %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %ghost %{_localstatedir}/run/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder %changelog +* Tue May 09 2017 Andrew Bauer - 1.30.4-1 +- modify autosetup macro parameters +- modify requirements for php-pecl-acpu-bc package +- 1.30.4 release + +* Tue May 02 2017 Andrew Bauer - 1.30.3-1 +- 1.30.3 release + +* Thu Mar 30 2017 Andrew Bauer - 1.30.2-2 +- 1.30.2 release + +* Wed Feb 08 2017 Andrew Bauer - 1.30.2-1 +- Bump version for 1.30.2 release candidate 1 + * Wed Dec 28 2016 Andrew Bauer - 1.30.1-2 - Changes from rpmfusion #4393 diff --git a/distros/ubuntu1204/NEWS b/distros/ubuntu1204/NEWS index 6200726cf..e69de29bb 100644 --- a/distros/ubuntu1204/NEWS +++ b/distros/ubuntu1204/NEWS @@ -1,10 +0,0 @@ -zoneminder (1.28.1-1) unstable; urgency=low - - This version is no longer automatically initialize or upgrade database. - See README.Debian for details. - - Changed installation paths (please correct your web server configuration): - /usr/share/zoneminder --> /usr/share/zoneminder/www - /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:12:17 +1100 diff --git a/distros/ubuntu1204/changelog b/distros/ubuntu1204/changelog index c7e86f69d..e69de29bb 100644 --- a/distros/ubuntu1204/changelog +++ b/distros/ubuntu1204/changelog @@ -1,573 +0,0 @@ -zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium - - * include api, switch to cmake build - - -- Isaac Connor Mon, 17 Aug 2015 10:29:23 -0400 - - -zoneminder (1.28.1-8) unstable; urgency=medium - - * Patchworks: - + New upstream "980-fix-image-size.patch". - + New "default_cgi-path.patch" to correct default ZM_PATH_ZMS. - * postinst: set "root" as group owner for "/var/log/zm" to silence - logrotate warnings. - * Minor correction to README.Debian. - - -- Dmitry Smirnov Sun, 16 Aug 2015 19:19:50 +1000 - -zoneminder (1.28.1-7) unstable; urgency=medium - - * Build-Depends += "cakephp (<< 3.0.0~)"; - Zoneminder is not compatible with latest CakePHP. - * Handle conffile removal from maintscript. - * rules: build man pages reproducibly. - * gbp.conf: renamed old style config section [git-dch] to [dch]. - * README - + added instructions to update owner of the "/etc/zm/zm.conf" - (Closes: #789327). - + zmupdate.pl needs CREATE rights. - + added note about required number of "fcgiwrap" workers. - * New upstream patch: "zmtrigger-plus.patch". - - -- Dmitry Smirnov Mon, 20 Jul 2015 16:30:15 +1000 - -zoneminder (1.28.1-6) unstable; urgency=low - - * New "zoneminder-doc" and "zoneminder-dbg" packages. - - -- Dmitry Smirnov Sun, 19 Apr 2015 14:50:41 +1000 - -zoneminder (1.28.1-5) unstable; urgency=low - - * Move handling of "/var/run/zm" and "/tmp/zm" from .service into .tmpfile. - Let dh_installinit do the job. Thanks, Andrew Bauer. - * Use dh_apache2 to install Apache conf file; remove old conf and symlink. - * Promote "libapache2-mod-php5 | php5-fpm" to Recommends. - * Build-Depends: - + dh-linktree - + cakephp (>= 2.6.3) - + libjs-jquery - + libjs-mootools - * Depends: - - libjs-jquery - - libjs-mootools - * Build-time replace bundled CakePHP with system one using "dh-linktree". - * Use "dh-linktree" to handle mootools and jquery symlinks. - - -- Dmitry Smirnov Sun, 19 Apr 2015 11:45:01 +1000 - -zoneminder (1.28.1-4) unstable; urgency=low - - * New patch to fix HTML export with USE_DEEP_STORAGE (closes: #723706). - * New "783.patch" to describe potential data loss in ZM_USE_DEEP_STORAGE. - * New patch to change default date format to region-neutral ISO notation - with time zone. - * Build sphinx documentation: - + Install "zoneminder.1" man page. - + Build-Depends += "python-sphinx | python3-sphinx" - + Added commented "zoneminder-doc" package. - + Added "docs.patch" to unlink distro-specific installation docs. - * rules: - + set ZM_CONTENTDIR, ZM_SOCKDIR and ZM_TMPDIR. - + remove mistakengly installed Perl module templates. - * Updated startup scripts to create ZM_TMPDIR. - * Hurd improvements: - + New patch to add PATH_MAX definitions. - + Build without MMAP support on Hurd. - + libsys-mmap-perl [!hurd-any]. - - -- Dmitry Smirnov Mon, 06 Apr 2015 18:18:55 +1000 - -zoneminder (1.28.1-3) unstable; urgency=low - - * Updated Apache2 and nginx configuration templates to support CGI. - * Updated README.Debian to document cgi-bin setup. - * Removed "/usr/share/zoneminder/www/cgi-bin" symlink. - * Added "apache2.patch" to correct Apache2 site configuration example. - * control: Suggests += "fcgiwrap". - * rules: added dh_systemd overrides to prevent automatic service - activation and start. - * Added note about manual service activation to README.Debian - (Closes: #781733). - - -- Dmitry Smirnov Thu, 02 Apr 2015 23:20:20 +1100 - -zoneminder (1.28.1-2) unstable; urgency=low - - * Removed word "Linux" from short package description. - * Build-Depends: do not require "libv4l-dev" on Hurd i.e. [!hurd-any]. - * Added run-time Perl Depends: - + libdbd-mysql-perl - + libimage-info-perl - + libmodule-load-conditional-perl - + libnet-sftp-foreign-perl - + liburi-encode-perl - * Prepare for package split: added commented "libzoneminder-perl" - and "zoneminder-dbg" packages to "debian/control". - * rules: do not install worthless ".packlist" file. - * Updated "libv4l1-videodev.h.patch" to fix v4lv1 detection in CMake. - - -- Dmitry Smirnov Thu, 02 Apr 2015 13:25:19 +1100 - -zoneminder (1.28.1-1) unstable; urgency=low - - [ Dmitry Smirnov ] - * New upstream release [February 2015]. - * Upload to unstable. - * Disabled automatic database upgrades: post(inst|rm) scripts no longer - touch database or do unexpected stuff (Closes: #779254). - See README.Debian for details. - * Updated installation paths: - + /usr/share/zoneminder --> /usr/share/zoneminder/www - + /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - * Added logrotate config (Closes: #544826). - Thanks, Alberto Reyes. - * Native systemd service; "--with systemd" added to dh. - * Build with CMake instead of autoconf; rules clean-up. - * Build with all hardening. - * Build and install "zmupdate.pl.1" man page. - * Added nginx/php5-fpm configuration example. - * Install upstream "apache.conf" example. - * Described setup of Zoneminer web site and database in README.Debian. - * Install "/etc/zm/zm.conf" with tighter permissions. - * Added TODO.Debian. - * Added "debian/clean"; "debian/gbp.conf"; bug-presubj. - * Remove bundled Cake tests to take ~5 MB off big-usr-share. - * Standards-Version: 3.9.6; compat/debhelper to version 9. - * Vcs links to new git repository at collab-maint. - * Build-Depends: - + dh-systemd - + libgcrypt11-dev --> libgcrypt-dev - + libcurl4-gnutls-dev - + libvlc-dev - + policykit-1 (required by "zmsystemctl.pl") - - dh-autoreconf, autoconf, automake - * Depends: - - apache2 - - libapache2-mod-php5 (moved to Suggests) - - libpcre3 (invalid) - - libmodule-load-perl (obsolete; replaced with perl-modules) - - libarchive-tar-perl (obsolete; replaced with perl-modules) - - mysql-server (moved to Recommends, Closes: #759504). - - php5 - + libav-tools - + libjs-jquery (replaces bundled component) - + libjs-mootool (replaces bundled component) - + libjson-any-perl (Closes: #690803). - + perl-modules (Closes: #745819). - * Recommends: - + apache2 | httpd - + mysql-server | virtual-mysql-server (Closes: #732874). - * Suggests: - + libapache2-mod-php5 | php5-fpm - + logrotate - * Refreshed, renamed and re-ordered patches; added DEP-3 headers. - * Removed "vendor_perl" patch (applied-upstream). - * New patches: - + cmake-fix-confpath.patch - + cmake-gnutls.patch - + cmake-nossl.patch - + cmake.patch - + format-hardening.patch - + pod_man_fixes.patch - + pod_name_fixes.patch - + pod_zmupdate-to-pod2usage.patch - * Lintianisation (incomplete): - - extra-license-file - - init.d-script-missing-lsb-description - - init.d-script-does-not-source-init-functions - - privacy-breach-generic - - package-contains-empty-directory - - manpage-has-errors-from-pod2man - - manpage-has-bad-whatis-entry - - quilt-patch-missing-description - - no-dep5-copyright - * Lintian-overrides: - + unusual-interpreter usr/bin/zmsystemctl.pl #!/usr/bin/pkexec - + script-not-executable usr/share/zoneminder/www/api/* - + script-with-language-extension usr/bin/*.pl - + source-is-missing web/tools/mootools/mootools-*-yc.js - + source-is-missing web/skins/*/js/jquery-1.4.2.min.js - + source-contains-prebuilt-javascript-object - * Renamed files in "debian". - * watch: dfsg repacksuffix and dversionmangle. - * "debian/copyright" to Copyright-Format-1.0. - * Set myself as new Maintainer (Closes: #760314). - - [ Vagrant Cascadian ] - * Removed obsolete DM-Upload-Allowed flag. - * Update debian/watch to use tarballs from github. - * Add Build-Depends on libgcrypt11-dev (Closes: #745819). - * Use canonical alioth Vcs-Hg URL. - * debian/control: Add Build-Depends: libpolkit-gobject-1-dev. - * Removed configure flag "--enable-crashtrace=no", which is no longer - present upstream. - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:11:13 +1100 - -zoneminder (1.26.5-3.1) experimental; urgency=low - - * Non-maintainer upload. - * Add libav10.patch and compile against libav10 (Closes: #739461) - - -- Reinhard Tartler Wed, 19 Mar 2014 00:31:22 +0000 - -zoneminder (1.26.5-3) unstable; urgency=low - - - * Previous release still didn't build on PPC - this has been corrected. - (Closes: #736516) - - -- Peter Howard Tue, 4 Feb 2014 02:02:10 +1000 - -zoneminder (1.26.5-2) unstable; urgency=low - - * Remove dependency on ffmpeg - (Closes: #721161) - - * Builds again on non-x86 target architectures. - - -- Peter Howard Thu, 23 Jan 2014 01:02:10 +1000 - -zoneminder (1.26.5-1) unstable; urgency=low - - * New upstream version - (Closes: #694131) - * Change Build-Depends on libgnutls-dev to libgnutls-openssl-dev - (Closes: #731560) - -- Peter Howard Tue, 17 Dec 2013 01:02:10 +1000 - -zoneminder (1.25.0-4) unstable; urgency=high - - * Add CVE-2013-0232 patch - [SECURITY] CVE-2013-0232: Shell escape commands with untrusted content. - Thanks to James McCoy (Closes: #698910) - Thanks also to Salvatore Bonaccorso - - -- Peter Howard Tue, 12 Jun 2013 12:02:10 +1000 - -zoneminder (1.25.0-3) unstable; urgency=low - - * debian/rules: Export CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS, to ensure - hardening build flags are enabled. - - -- Vagrant Cascadian Tue, 28 Aug 2012 12:10:03 -0700 - -zoneminder (1.25.0-2) unstable; urgency=low - - [ Vagrant Cascadian ] - * Add a patch to disable checking for updated versions by default, as - upgrades should happen through package management. - * Use dpkg-buildflags in debian/rules to set default compiler flags. - * Ensure zoneminder is stopped before starting (Closes: #657407). - - [ Peter Howard ] - * Fix postinst to add permission for table creation during upgrade - (Closes: #657407). - - -- Vagrant Cascadian Thu, 23 Aug 2012 12:40:34 -0700 - -zoneminder (1.25.0-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "ftbfs with GCC-4.7": add patch Fix-FTBFS-with-gcc-4.7 from Cyril - Brulebois: fix missing includes. - (Closes: #667428) - - -- gregor herrmann Sun, 13 May 2012 17:02:21 +0200 - -zoneminder (1.25.0-1) unstable; urgency=low - - * Fix typo in libv4l1-videodev.h patch that caused v4l1 support to be - dropped. - * Fail to build if version in postinst doesn't match upstream version. - * Add Build-Depends: libavdevice-dev to fix MPEG streaming (Closes: #515558). - * debian/rules: Convert to using debhelper overrides. - * Set debian/compat to 7. - * Simplify debian/watch file. - * Refresh debian/patches/use_libjs-mootools. - * Refresh debian/patches/libv4l1-videodev.h. - * Remove dependencies on php4 and related packages. - * Remove build-dependencies on libmysqlclient14-dev and - libmysqlclient15-dev. - * Update Build-Depends to use libjpeg-dev instead of libjpeg62-dev - (Closes: #647114). - * Add patch to fix build by testing for C headers rather than C++ headers. - Thanks to Ryan Niebur. (Closes: #654230) - * Add a patch to fix build problems caused by API changes in libav 0.8. - Thanks again to Ryan Niebur. (Closes: #654230) - - -- Vagrant Cascadian Mon, 16 Jan 2012 11:58:05 -0800 - -zoneminder (1.24.4-1) unstable; urgency=low - - [ Peter Howard ] - * Initial release of 1.24.4 (Closes: #634985). - - Fix 32/64-bit type declarations (Closes: #614404). - * Update patches. - - [ Vagrant Cascadian ] - * Add patch to fix FTBFS by using libv4l1-videodev.h from libv4l-dev. - Thanks to Andreas Metzler for reporting the issue. - (Closes: #619813). - * Document adding the www-data user to the video group in README.Debian. - (Closes: #611324) - * Depend on libsys-mmap-perl to enable mapped memory support. - (Closes: #607331) - * Update libjs-mootools patch to use -nc variants (Closes: #635075). - * Depend on javascript-common, to ensure that /javascript is available in - the web server. - * Set the upstream version in postinst at build time. - * Use dh-autoreconf to properly clean up autogenerated files during build. - * Add Vcs-HG to debian/control. - * Add Build-Depends: libv4l-dev, libbz2-dev, dh-autoreconf, libsys-mmap-perl. - - -- Vagrant Cascadian Sun, 24 Jul 2011 16:44:30 +0200 - -zoneminder (1.24.2-9) unstable; urgency=low - - * Apply patch from Ubuntu to fix FTBFS with ffmpeg 0.6: - - Add -D__STDC_CONSTANT_MACROS to CPPFLAGS (closes: 614080). - * Update Standards-Version to 3.9.1, no changes necessary. - - -- Vagrant Cascadian Sun, 20 Feb 2011 23:43:02 -0800 - -zoneminder (1.24.2-8) unstable; urgency=medium - - [ Vagrant Cascadian ] - * Apply patch to fix V4L2 cameras without crop support (closes: #608790). - Thanks to piratebab. - * Add preinst script which aborts if dangerous symlinks exist. - (closes: #608793) - - [ Peter Howard ] - * Added to README.Debian with info about images and events directories. - (closes: #608793) - - -- Vagrant Cascadian Sat, 15 Jan 2011 19:39:26 -0800 - -zoneminder (1.24.2-7) unstable; urgency=medium - - * Do not set ownership of /var/cache/zoneminder when upgrading, which fixes a - regression causing upgrades to take inordinately long with large - installations (closes: #597040). - - -- Vagrant Cascadian Fri, 17 Sep 2010 11:24:41 -0700 - -zoneminder (1.24.2-6) unstable; urgency=low - - * Only remove database on purge. This requires only creating the database if - it doesn't already exist, and upgrading the database only if the database - is an older version (closes: #497107). - - * Do not prompt the user on database upgrades by using the --nointeractive - flag when calling zmupdate.pl from postinst (closes: #595902). - - -- Vagrant Cascadian Fri, 10 Sep 2010 10:06:06 -0700 - -zoneminder (1.24.2-5) unstable; urgency=low - - [ Peter Howard ] - * Add zip dependency - (closes: #494261) - * Add debian/watch file - (closes: #545552) - * Use packaged libjs-mootools - (closes: #585590) - * Miscellaneous cleanups - - [ Vagrant Cascadian ] - * Add vagrant@debian.org as uploader - * Update Standards-Version to 3.9.0, no changes necessary. - - -- Vagrant Cascadian Fri, 23 Jul 2010 18:12:50 -0500 - -zoneminder (1.24.2-4.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "package removed, processes still running": apply patch to - debian/postinst by Vagrant Cascadian: use invoke-rc.d and run - mysql-related actions only when mysql is running (closes: #583648). - - -- gregor herrmann Thu, 01 Jul 2010 19:47:10 +0200 - -zoneminder (1.24.2-4) unstable; urgency=high - * Update init.d to list mysql dependency - (closes: #583505) - * Change dependency from libmime-perl to libmime-tools-perl - (closes: #585589) - * Problems in changelog format fixed - (closes: #585592) - * Fix debian-rules-ignores-make-clean-error - (closes: #585593) - -- Peter Howard Mon, 14 jun 2010 15:02:10 +1000 - -zoneminder (1.24.2-3) unstable; urgency=high - * Changes symbols to build with libjpeg8 - (closes: #565326, #568327) - * Note: location of all perl files should have been fixed in previous release - (closes: #553096) - -- Peter Howard Mon, 26 apr 2010 15:02:10 +1000 - -zoneminder (1.24.2-2) unstable; urgency=high - - * Remove custom perl parth from zmpkg.pl, fix location of manpages. - (closes: #551746, #553092) - * Fix GCC4.4 bug - (closes: #531717) - * Fix potential bug in postinst script - - -- Peter Howard Sat, 14 Nov 2009 15:02:10 +1000 - -zoneminder (1.24.2-1) unstable; urgency=high - - * Initial release of zoneminder 1.24.2 - -- Peter Howard Fri, 11 Sep 2009 07:02:50 +1000 - -zoneminder (1.24.1-1) unstable; urgency=high - - * Initial release of zoneminder 1.24.1, closing CVE-2008-3882, - CVE-2008-3881, CVE-2008-3880 - (closes: #497640) - * Change syslog dependency to rsyslog. - (closes: #526918) - * Add missing perl dependency. - * Restore patch to disable "check for updates" by default. - * Removed spurious '$' in init script. - (closes: #486064) - * Change permission of zm.conf from 0600 to 0400 for CVE-2008-6755 - (closes: #528252) - -- Peter Howard Sat, 16 May 2009 07:02:50 +1000 - -zoneminder (1.23.3-4) unstable; urgency=high - - * update to get it building with latest unstable. Thanks to waldi@debian.org - (closes: #517569) - -- Peter Howard Thu, 16 Apr 2009 01:02:50 +1000 - -zoneminder (1.23.3-3) unstable; urgency=high - - * ffmpeg confirmed working - (closes: #475145) - * Fix upgrade problem intrudouced in 1.23.3-1 - (closes: #481637) - * Include libmime-lite-perl in dependencies - (closes: #486312) - -- Peter Howard Thu, 18 Sep 2008 01:02:50 +1000 - -zoneminder (1.23.3-2) unstable; urgency=high - - * ffmpeg finally working? - - -- Peter Howard Wed, 13 Aug 2008 01:02:50 +1000 - -zoneminder (1.23.3-1) unstable; urgency=high - - * Initial version for 1.23.3 - security fix. - (closes: #479034) - - -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 - -zoneminder (1.23.2-2) unstable; urgency=low - - * Update to init.d - (closes: #468856) - * Add dependency on logging daemon - (closes: #471277) - - -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 - -zoneminder (1.23.2-1) unstable; urgency=low - - * Initial version for 1.23.2 - (closes: #464152) - * Zoneminder 1.23.2 upstream includes fix for GCC 4.3 - (closes: #454980) - * Includes ffmpeg patch by Alexander Kushnirenko - - -- Peter Howard Sat, 01 Mar 2008 16:02:50 +1000 - -zoneminder (1.22.3-10) unstable; urgency=low - - * Fix bug introduced in -9 where perl is put under /usr/local - (closes: #457507) - - -- Peter Howard Mon, 24 Dec 2007 16:02:50 +1000 - -zoneminder (1.22.3-9) unstable; urgency=low - - * Starting zoneminder via init script now invokes "zmfix -a" - (closes: #481637) - * Change apache2-mpm-prefork dependency to apache2 - * Temp dir for export under /var/cache/zoneminder (but linked back to - /usr/share/zoneminder for now) - * Redo use of gnutls rather than openssl for md5 hashes - - -- Peter Howard Mon, 10 Dec 2007 16:02:50 +1000 - -zoneminder (1.22.3-8) unstable; urgency=low - - * Build now includes libpcre3 - (closes: #437533) - * "Monitor Presets" patch now applied to package during build. - - -- Peter Howard Sat, 18 Aug 2007 14:35:23 +1000 - -zoneminder (1.22.3-7) unstable; urgency=low - - * Turn off debug trace and crash dump on build - (closes:#414857,#414891) - * Additional perl libraries added in dependencies - (closes:#416291) - * Change preferred PHP version from 4 to 5 - -- Peter Howard Sun, 29 Jul 2007 15:11:13 +1000 - -zoneminder (1.22.3-6) unstable; urgency=low - - * Removed a similar bash only statement from zmpkg.pl - (closes:414882) - - -- Peter Howard Sat, 14 Apr 2007 11:46:56 +1000 - -zoneminder (1.22.3-5) unstable; urgency=low - - * Installs with "phone home" feature turned off by default, and permissions - on /etc/zm/zm.conf fixed (now the 0600 it s hould be) - (closes:415349) - * Removed "stupid bash-ism" on mysqld check in postinst file. - - -- Peter Howard Fri, 6 Apr 2007 15:50:00 +1000 - -zoneminder (1.22.3-4) unstable; urgency=low - - * Put libmysqlclient-15-dev in front of -14-dev so sbuild works - (closes: #414410) - - -- Peter Howard Mon, 12 Mar 2007 11:38:56 +1100 - -zoneminder (1.22.3-3) unstable; urgency=low - - * Clean up of postinstall, postrm ; user "zm" definitely was a mistake - * Also in postinstall: check and start MySQL if it's not running. - * init.d script now checks if zoneminder isn't running and still returns 0 - (which helps uninstalling) - * Addition of php5 dependency options as well as php4. - - -- Peter Howard Mon, 26 Feb 2007 10:40:52 +1100 - -zoneminder (1.22.3-2) unstable; urgency=low - - * Added zmuser in the mysql creation; this should fix the install problem - for people, but needs to be cleaned up (in -3) - - -- Peter Howard Fri, 16 Feb 2007 14:16:03 +1100 - -zoneminder (1.22.3-1) unstable; urgency=low - - * Initial Version. (closes: #248393) - * Patched out use of openssl; uses gnutls instead for MD5 hashes. - * Removed MakeMaker-inserted Perl licensing (with authors permission) in - various scripts; replaced with GPL. - - -- Peter Howard Wed, 7 Feb 2007 14:09:01 +1100 diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index d7c2232cf..cfd1c81a6 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -6,6 +6,7 @@ Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake ,libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libavdevice-dev + ,libboost1.55-dev ,libbz2-dev ,libgcrypt-dev ,libcurl4-gnutls-dev @@ -54,7 +55,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libdata-uuid-perl ,mysql-client | virtual-mysql-client ,perl-modules - ,php5-mysql, php5-gd + ,php5-mysql, php5-gd, php5-apcu, php-apc ,policykit-1 ,rsyslog | system-log-daemon ,zip diff --git a/distros/ubuntu1204/patches/default_cgi-path.patch b/distros/ubuntu1204/patches/default_cgi-path.patch deleted file mode 100644 index 8bfc2ba06..000000000 --- a/distros/ubuntu1204/patches/default_cgi-path.patch +++ /dev/null @@ -1,16 +0,0 @@ -Last-Update: 2015-08-16 -Forwarded: no -Author: Dmitry Smirnov -Description: correct path to CGI app according to default web server configuration. - ---- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in -+++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in -@@ -428,7 +428,7 @@ our @options = - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/zm/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" - The ZoneMinder streaming server is required to send streamed diff --git a/distros/ubuntu1204/patches/series b/distros/ubuntu1204/patches/series index fc70f4006..e69de29bb 100644 --- a/distros/ubuntu1204/patches/series +++ b/distros/ubuntu1204/patches/series @@ -1,2 +0,0 @@ -default_cgi-path.patch -use_libjs-mootools.patch diff --git a/distros/ubuntu1204/patches/use_libjs-mootools.patch b/distros/ubuntu1204/patches/use_libjs-mootools.patch deleted file mode 100644 index b3925f6d0..000000000 --- a/distros/ubuntu1204/patches/use_libjs-mootools.patch +++ /dev/null @@ -1,18 +0,0 @@ -Last-Update: 2015-03-29 -Forwarded: no -Bug-Debian: http://bugs.debian.org/585590 -Reviewed-By: Dmitry Smirnov -Description: use mootools shipped by debian, rather than the zoneminder included mootools. - ---- a/web/skins/classic/includes/functions.php -+++ b/web/skins/classic/includes/functions.php -@@ -63,9 +63,8 @@ - } - ?> - - -- - - - >/dev/null 2>&1 || : endscript - weekly - rotate 3 + daily + rotate 7 } diff --git a/distros/ubuntu1204/zoneminder.tmpfile b/distros/ubuntu1204/zoneminder.tmpfile index d307c6640..017955900 100644 --- a/distros/ubuntu1204/zoneminder.tmpfile +++ b/distros/ubuntu1204/zoneminder.tmpfile @@ -1,2 +1,3 @@ d /var/run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data diff --git a/distros/ubuntu1504_cmake_split_packages/apache.conf b/distros/ubuntu1504_cmake_split_packages/apache.conf index 292581e78..59efc6248 100644 --- a/distros/ubuntu1504_cmake_split_packages/apache.conf +++ b/distros/ubuntu1504_cmake_split_packages/apache.conf @@ -8,7 +8,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Alias /zm /usr/share/zoneminder/www - Options -Indexes +ollowSymLinks + Options -Indexes +FollowSymLinks DirectoryIndex index.php diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 9e2ec96e7..1adea5494 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -5,6 +5,8 @@ Maintainer: Dmitry Smirnov Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake + ,libx264-dev, libmp4v2-dev + ,libboost-dev ,libavdevice-dev (>= 6:10~) ,libavcodec-dev (>= 6:10~) ,libavformat-dev (>= 6:10~) @@ -14,7 +16,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libgcrypt-dev ,libcurl4-gnutls-dev ,libgnutls-openssl-dev - ,libjpeg-dev + ,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev ,libmysqlclient-dev ,libpcre3-dev ,libpolkit-gobject-1-dev @@ -25,11 +27,11 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libphp-serialization-perl ,libsys-mmap-perl [!hurd-any] ,libwww-perl - ,libdata-uuid-perl + ,libdata-uuid-perl # Unbundled (dh_linktree): ,libjs-jquery ,libjs-mootools -Standards-Version: 3.9.6 +Standards-Version: 3.9.8 Homepage: http://www.zoneminder.com/ Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git Vcs-Git: git://anonscm.debian.org/collab-maint/zoneminder.git @@ -38,11 +40,10 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common + ,libmp4v2-2, libx264-142|libx264-148, libswscale-ffmpeg3|libswscale4|libswscale3 ,ffmpeg | libav-tools - ,libdate-manip-perl + ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl - ,libmime-lite-perl - ,libmime-tools-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl ,libnet-sftp-foreign-perl @@ -60,13 +61,14 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libio-socket-multicast-perl ,libdigest-sha-perl ,libsys-cpu-perl, libsys-meminfo-perl - ,libdata-uuid-perl + ,libdata-uuid-perl ,mysql-client | virtual-mysql-client ,perl-modules - ,php5-mysql | php-mysql, php5-gd | php-gd + ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc ,policykit-1 ,rsyslog | system-log-daemon ,zip + ,libpcre3 Recommends: ${misc:Recommends} ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm ,mysql-server | virtual-mysql-server diff --git a/distros/ubuntu1604/patches/series b/distros/ubuntu1604/patches/series index fc70f4006..e69de29bb 100644 --- a/distros/ubuntu1604/patches/series +++ b/distros/ubuntu1604/patches/series @@ -1,2 +0,0 @@ -default_cgi-path.patch -use_libjs-mootools.patch diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules index bf8012aa8..e3b235be8 100755 --- a/distros/ubuntu1604/rules +++ b/distros/ubuntu1604/rules @@ -28,7 +28,7 @@ override_dh_auto_configure: override_dh_clean: dh_clean $(MANPAGES1) - $(RM) -r docs/_build docs/installationguide + $(RM) -r docs/_build build-indep: #$(MAKE) -C docs text @@ -58,8 +58,10 @@ override_dh_auto_install: override_dh_fixperms: dh_fixperms - ## 637685 - chmod -c o-r $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + # + # As requested by the Debian Webapps Policy Manual §3.2.1 + chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf override_dh_installinit: dh_installinit --no-start diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu1604/source/format index 89ae9db8f..163aaf8d8 100644 --- a/distros/ubuntu1604/source/format +++ b/distros/ubuntu1604/source/format @@ -1 +1 @@ -3.0 (native) +3.0 (quilt) diff --git a/distros/ubuntu1604/watch b/distros/ubuntu1604/watch deleted file mode 100644 index 7ee690edb..000000000 --- a/distros/ubuntu1604/watch +++ /dev/null @@ -1,7 +0,0 @@ -version=3 - -opts=\ -repacksuffix=+dfsg,\ -dversionmangle=s{\+dfsg\d*}{},\ - https://github.com/ZoneMinder/ZoneMinder/releases \ - .*/ZoneMinder/archive/v(.*).tar.gz diff --git a/distros/ubuntu1604/zoneminder.install b/distros/ubuntu1604/zoneminder.install index 8a26777c0..b0b1ad5b6 100644 --- a/distros/ubuntu1604/zoneminder.install +++ b/distros/ubuntu1604/zoneminder.install @@ -3,6 +3,7 @@ usr/bin usr/lib/zoneminder usr/share/polkit-1 usr/share/zoneminder/db +usr/share/zoneminder/icons usr/share/zoneminder/www # libzoneminder-perl files: diff --git a/distros/ubuntu1604/zoneminder.links b/distros/ubuntu1604/zoneminder.links index 4299f392a..14a8aee91 100644 --- a/distros/ubuntu1604/zoneminder.links +++ b/distros/ubuntu1604/zoneminder.links @@ -1,3 +1,4 @@ /var/cache/zoneminder/events /usr/share/zoneminder/www/events /var/cache/zoneminder/images /usr/share/zoneminder/www/images /var/cache/zoneminder/temp /usr/share/zoneminder/www/temp +/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/ubuntu1604/zoneminder.logrotate index 18fc6b0a0..59238b7fe 100644 --- a/distros/ubuntu1604/zoneminder.logrotate +++ b/distros/ubuntu1604/zoneminder.logrotate @@ -2,6 +2,8 @@ missingok notifempty sharedscripts + delaycompress + compress postrotate /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : endscript diff --git a/distros/ubuntu1604/zoneminder.menu b/distros/ubuntu1604/zoneminder.menu new file mode 100644 index 000000000..fa3f3ff36 --- /dev/null +++ b/distros/ubuntu1604/zoneminder.menu @@ -0,0 +1,2 @@ +?package(zoneminder):needs="x11" section="Applications/Video" title="ZoneMinder" command="/usr/bin/x-www-browser http://localhost/zm" icon="/usr/share/zoneminder/icons/16x16/icon.xpm" + diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst index 05f0c0448..a8f93c4d0 100644 --- a/distros/ubuntu1604/zoneminder.postinst +++ b/distros/ubuntu1604/zoneminder.postinst @@ -12,6 +12,10 @@ if [ "$1" = "configure" ]; then if [ -z "$2" ]; then chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* fi + if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ]; then + echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." + a2enmod cgi + fi # Do this every time the package is installed or upgraded diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu1604/zoneminder.tmpfile index d307c6640..ef68288ba 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu1604/zoneminder.tmpfile @@ -1,2 +1,3 @@ -d /var/run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data +d /var/run/zm 0755 www-data www-data +d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data diff --git a/docs/faq.rst b/docs/faq.rst index 1460e034d..9228274d7 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -309,7 +309,7 @@ The main causes are. * Capture frame rates. Unless there's a compelling reason in your case there is often little benefit in running cameras at 25fps when 5-10fps would often get you results just as good. Try changing your monitor settings to limit your cameras to lower frame rates. You can still configure ZM to ignore these limits and capture as fast as possible when motion is detected. * Run function. Obviously running in Record or Mocord modes or in Modect with lots of events generates a lot of DB and file activity and so CPU and load will increase. * Basic default detection zones. By default when a camera is added one detection zone is added which covers the whole image with a default set of parameters. If your camera covers a view in which various regions are unlikely to generate a valid alarm (ie the sky) then I would experiment with reducing the zone sizes or adding inactive zones to blank out areas you don't want to monitor. Additionally the actual settings of the zone themselves may not be optimal. When doing motion detection the number of changed pixels above a threshold is examined, then this is filter, then contiguous regions are calculated to see if an alarm is generated. If any maximum or minimum threshold is exceeded according to your zone settings at any time the calculation stops. If your settings always result in the calculations going through to the last stage before being failed then additional CPU time is used unnecessarily. Make sure your maximum and minimumzone thresholds are set to sensible values and experiment by switching RECORD_EVENT_STATS on and seeing what the actual values of alarmed pixels etc are during sample events. - * Optimise your settings. After you've got some settings you're happy with then switching off RECORD_EVENT_STATS will prevent the statistics being written to the database which saves some time. Other settings which might make a difference are ZM_FAST_RGB_DIFFS, ZM_OPT_FRAME_SERVER and the JPEG_xxx_QUALITY ones. + * Optimise your settings. After you've got some settings you're happy with then switching off RECORD_EVENT_STATS will prevent the statistics being written to the database which saves some time. Other settings which might make a difference are ZM_FAST_RGB_DIFFS and the JPEG_xxx_QUALITY ones. I'm sure there are other things which might make a difference such as what else you have running on the box and memory sizes (make sure there's no swapping going on). Also speed of disk etc will make some difference during event capture and also if you are watching the whole time then you may have a bunch of zms processes running also. diff --git a/docs/installationguide/multiserver.rst b/docs/installationguide/multiserver.rst index e9ebefac2..14e52d29f 100644 --- a/docs/installationguide/multiserver.rst +++ b/docs/installationguide/multiserver.rst @@ -42,8 +42,6 @@ Note that these commands are just an example and might not be secure enough for 7. If you have chosen to change the ZoneMinder database account credentials to something other than zmuser/zmpass, you must now update zm.conf on each ZoneMinder Server. Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous step. -Additionally, you must also edit /usr/share/zoneminder/www/api/app/Config/database.php in a similar manner on each ZoneMinder Server. Scroll down and change login and password to the values you created in the previous step. - 8. All ZoneMinders Servers must share a common events folder. This can be done in any manner supported by the underlying operating system. From the Storage Server, share/export a folder to be used for ZoneMinder events. 9. From each ZoneMinder Server, mount the shared events folder on the Storage Server to the events folder on the local ZoneMinder Server. diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index e6b506ac5..d662d1812 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -169,8 +169,6 @@ Now clone the ZoneMinder git repository: cd git clone https://github.com/ZoneMinder/ZoneMinder cd ZoneMinder - git submodule init - git submodule update This will create a sub-folder called ZoneMinder, which will contain the latest development. @@ -180,21 +178,21 @@ We want to turn this into a tarball, but first we need to figure out what to nam ls ~/rpmbuild/SOURCES -The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From one folder above the local ZoneMinder git repository, execute the following: +The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From the root folder of the local ZoneMinder git repository, execute the following: :: - mv ZoneMinder ZoneMinder-1.28.1 - tar -cvzf ~/rpmbuild/SOURCES/ZoneMinder-1.28.1.tar.gz ZoneMinder-1.28.1/* + git archive --prefix=ZoneMinder-1.28.1/ -o ~/rpmbuild/SOURCES/zoneminder-1.28.1.tar.gz HEAD -The trailing "/\*" leaves off the hidden dot "." file and folders from the git repo, which is what we want. Note that we are overwriting the original tarball. If you wish to keep the original tarball then create a copy prior to creating the new tarball. -Now build a new src.rpm: +From the root of the local ZoneMinder git repo, execute the following: :: - rpmbuild -bs --nodeps ~/rpmbuild/SPECS/zoneminder.el7.spec + rpmbuild -bs --nodeps distros/redhat/zoneminder.spec + +Notice we used the rpm specfile that is part of the latest master branch you just downloaded, rather than the one that may be in your ~/rpmbbuild/SOURCES folder. This step will overwrite the SRPM you originally downloaded, so you may want to back it up prior to completing this step. Note that the name of the specfile will vary slightly depending on the target distro. diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 6e7ac45e4..e561e7477 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -287,19 +287,19 @@ To build the latest master snapshot: :: - ./do_debian_package.sh `lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'` `date +%Y%m%d`01 local master + ./do_debian_package.sh --snapshot=NOW --branch=master --type=local To build the latest stable release: :: - ./do_debian_package.sh `lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'` `date +%Y%m%d`01 local stable + ./do_debian_package.sh --snapshot=stable --type=local -Note that the ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` -part simply extracts your distribution name - like "vivid", "trusty" etc. You -can always replace it by your distro name if you know it. As far as the script +Note that the distribution will be guessed using ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` +which simply extracts your distribution name - like "vivid", "trusty" etc. You +can always specify it using --distro=your distro name if you know it. As far as the script goes, it checks if your distro is "trusty" in which case it pulls in pre-systemd release configurations and if its not "trusty" it assumes its based on systemd and pulls in systemd related config files. @@ -358,24 +358,6 @@ Changed Default DB User ^^^^^^^^^^^^^^^^^^^^^^^ If you have changed your DB login/password from zmuser/zmpass, you need to -update these values in zm.conf and the API's database.php file. +update these values in zm.conf. 1. Edit zm.conf to change ZM_DB_USER and ZM_DB_PASS to the values you used. - -2. Edit databse.php which can be found in the web server folder zoneminder/www/api/app/Config - -There is a class there called DATABASE_CONFIG - -change the $default array to reflect your new details. Example: - -:: - - public $default = array( - 'datasource' => 'Database/Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'login' => 'mynewDBusername', - 'password' => 'mynewDBpassword' - 'database' => 'zm', - 'prefix' => '', - //'encoding' => 'utf8', - ); \ No newline at end of file diff --git a/docs/userguide/definezone.rst b/docs/userguide/definezone.rst index 24a6bb8b3..cf33f44c9 100644 --- a/docs/userguide/definezone.rst +++ b/docs/userguide/definezone.rst @@ -40,6 +40,8 @@ Type Preset The preset chooser sets sensible default values based on computational needs (fast v. best) and sensitivity (low, medium, high.) It is not required that you select a preset, and you can alter any of the parameters after choosing a preset. For a small number of monitors with ZoneMinder running on modern equipment, Best, high sensitivity can be chosen as a good starting point. + It is important to understand that the available presets are intended merely as a starting point. Since every camera's view is unique, they are not guaranteed to work properly in every case. Presets tend to work acceptably for indoor cameras, where the objects of interest are relatively close and there typically are few or no unwanted objects moving within the cameras view. Presets, on the other hand, tend to not work acceptably for outdoor cameras, where the field of view is typically much wider, objects of interest are farther away, and changing weather patterns can cause false triggers. For outdoor cameras in particular, you will almost certainly have to tune your motion detection zone to get desired results. Please refer to `this guide `__ to learn how to do this. + Units * Pixels - Selecting this option will allow many of the following values to be entered (or viewed) in units of pixels. * Percentage - Selecting this option will allow may of the following values to be entered (or viewed) as a percentage. The sense of the percentage values refers to the area of the zone and not the image as a whole. This makes trying to work out necessary sizes rather easier. diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index bc0dcffe2..8477ef157 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -29,7 +29,7 @@ Here is what the filter window looks like events later and also make sure archived events don't get deleted, for example * Email details of all matches: Sends an email to the configured address with details about the event. The email can be customized as per TBD - * Execute command on all matches: Allows you to execute any arbitrary command on the matched events + * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. * Delete all matches: Deletes all the matched events * *E*: Use 'Submit' to 'test' your matching conditions. This will just match and show you what filters match. Use 'Execute' to actually execute the action after matching your conditions. Use 'Save' to save the filter for future use and 'Reset' to clear your settings diff --git a/docs/userguide/images/README.TXT b/docs/userguide/images/README.TXT new file mode 100644 index 000000000..21f85c8c3 --- /dev/null +++ b/docs/userguide/images/README.TXT @@ -0,0 +1,2 @@ +The XML images present in this folder have been drawn using http://draw.io +To edit images, simple go to draw.io and load the .xml files diff --git a/docs/userguide/images/zm-system-overview.jpg b/docs/userguide/images/zm-system-overview.jpg index 1624e44c6..de02dcf9e 100644 Binary files a/docs/userguide/images/zm-system-overview.jpg and b/docs/userguide/images/zm-system-overview.jpg differ diff --git a/docs/userguide/images/zm-system-overview.xml b/docs/userguide/images/zm-system-overview.xml index d0d55d5d0..e4314666a 100644 --- a/docs/userguide/images/zm-system-overview.xml +++ b/docs/userguide/images/zm-system-overview.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/userguide/options/options_system.rst b/docs/userguide/options/options_system.rst index 7df3d259a..53e560fe5 100644 --- a/docs/userguide/options/options_system.rst +++ b/docs/userguide/options/options_system.rst @@ -33,10 +33,6 @@ RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the AUDIT_CHECK_INTERVAL - The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. The default check interval of 900 seconds (15 minutes) is fine for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. -OPT_FRAME_SERVER - In some circumstances it is possible for a slow disk to take so long writing images to disk that it causes the analysis daemon to fall behind especially during high frame rate events. Setting this option to yes enables a frame server daemon (zmf) which will be sent the images from the analysis daemon and will do the actual writing of images itself freeing up the analysis daemon to get on with other things. Should this transmission fail or other permanent or transient error occur, this function will fall back to the analysis daemon. - -FRAME_SOCKET_SIZE - For large captured images it is possible for the writes from the analysis daemon to the frame server to fail as the amount to be written exceeds the default buffer size. While the images are then written by the analysis daemon so no data is lost, it defeats the object of the frame server daemon in the first place. You can use this option to indicate that a larger buffer size should be used. Note that you may have to change the existing maximum socket buffer size on your system via sysctl (or in /proc/sys/net/core/wmem_max) to allow this new size to be set. Alternatively you can change the default buffer size on your system in the same way in which case that will be used with no change necessary in this option - OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off. OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index 1b3bd7c5a..016f19881 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -12,7 +12,8 @@ configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zonemind # Do not install the misc files by default #install(FILES "${CMAKE_CURRENT_BINARY_DIR}/apache.conf" "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/misc") -# Install Policykit rules and actions into the proper folders -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") - +# Install Policykit rules and actions into the proper folders only on systems with systemd +if(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") +endif(WITH_SYSTEMD) diff --git a/misc/apache.conf.in b/misc/apache.conf.in index 5ad45338b..d1c6d70db 100644 --- a/misc/apache.conf.in +++ b/misc/apache.conf.in @@ -5,13 +5,22 @@ # Some values may need to manually adjusted to suit your setup # - ServerName @WEB_HOST@ ServerAdmin webmaster@localhost DocumentRoot "@WEB_PREFIX@" + Alias /zm/ "@WEB_PREFIX@/" Options -Indexes +FollowSymLinks AllowOverride All + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + ScriptAlias /cgi-bin "@CGI_PREFIX@" diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in index d15c479c2..d1cfb36a0 100644 --- a/misc/zoneminder.service.in +++ b/misc/zoneminder.service.in @@ -12,7 +12,7 @@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop -PIDFile="@ZM_RUNDIR@/zm.pid" +PIDFile=@ZM_RUNDIR@/zm.pid Environment=TZ=:/etc/localtime [Install] diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 37f93fd12..5846a39d2 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -26,9 +26,10 @@ configure_file(zm.in "${CMAKE_CURRENT_BINARY_DIR}/zm" @ONLY) #configure_file(zmeventdump.in zmeventdump @ONLY) # Generate man files for the perl scripts destined for the bin folder -file(GLOB perlscripts RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" "*.pl") +file(GLOB perlscripts "*.pl") FOREACH(PERLSCRIPT ${perlscripts}) - POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${PERLSCRIPT} zoneminder-${PERLSCRIPT} 8) + get_filename_component(PERLSCRIPTNAME ${PERLSCRIPT} NAME) + POD2MAN(${PERLSCRIPT} zoneminder-${PERLSCRIPTNAME} 8) ENDFOREACH(PERLSCRIPT ${perlscripts}) # Install the perl scripts diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 486468cc8..2ba12dfa2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -129,7 +129,7 @@ BEGIN { } # end BEGIN sub loadConfigFromDB { - print( "Loading config from DB\n" ); + print( "Loading config from DB" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to load options from database: $DBI::errstr\n" ); @@ -160,11 +160,12 @@ sub loadConfigFromDB { $option_count++;; } $sth->finish(); + print( " $option_count entries\n" ); return( $option_count ); } # end sub loadConfigFromDB sub saveConfigToDB { - print( "Saving config to DB\n" ); + print( "Saving config to DB " . @options . " entries\n" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to save options to database: $DBI::errstr\n" ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 3f80748db..29efe85ca 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -49,7 +49,7 @@ our %EXPORT_TAGS = ( ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'data'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{data} } ); our @EXPORT = qw(); @@ -72,152 +72,146 @@ sub INIT { # Types our %types = ( string => { - db_type => "string", - hint => "string", - pattern => qr|^(.+)$|, - format => q( $1 ) - }, - alphanum => { - db_type => "string", - hint => "alphanumeric", - pattern => qr|^([a-zA-Z0-9-_]+)$|, - format => q( $1 ) - }, - text => { - db_type => "text", - hint => "free text", - pattern => qr|^(.+)$|, - format => q( $1 ) - }, - boolean => { - db_type => "boolean", - hint => "yes|no", - pattern => qr|^([yn])|i, - check => q( $1 ), - format => q( ($1 =~ /^y/) ? "yes" : "no" ) - }, - integer => { - db_type => "integer", - hint => "integer", - pattern => qr|^(\d+)$|, - format => q( $1 ) - }, - decimal => { - db_type => "decimal", - hint => "decimal", - pattern => qr|^(\d+(?:\.\d+)?)$|, - format => q( $1 ) - }, - hexadecimal => { - db_type => "hexadecimal", - hint => "hexadecimal", - pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, - format => q( "0x".$1 ) - }, - tristate => { - db_type => "string", - hint => "auto|yes|no", - pattern => qr|^([ayn])|i, check=>q( $1 ), - format => q( ($1 =~ /^y/) ? "yes" : ($1 =~ /^n/ ? "no" : "auto" ) ) - }, - abs_path => { - db_type => "string", - hint => "/absolute/path/to/somewhere", - pattern => qr|^((?:/[^/]*)+?)/?$|, - format => q( $1 ) - }, - rel_path => { - db_type => "string", - hint => "relative/path/to/somewhere", - pattern => qr|^((?:[^/].*)?)/?$|, - format => q( $1 ) - }, - directory => { - db_type => "string", - hint => "directory", - pattern => qr|^([a-zA-Z0-9-_.]+)$|, - format => q( $1 ) - }, - file => { - db_type => "string", - hint => "filename", - pattern => qr|^([a-zA-Z0-9-_.]+)$|, - format => q( $1 ) - }, - hostname => { - db_type => "string", - hint => "host.your.domain", - pattern => qr|^([a-zA-Z0-9_.-]+)$|, - format => q( $1 ) - }, - url => { - db_type => "string", - hint => "http://host.your.domain/", - pattern => qr|^(?:http://)?(.+)$|, - format => q( "http://".$1 ) - }, - email => { - db_type => "string", - hint => "your.name\@your.domain", - pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, - format => q( $1\@$2 ) - }, - ); - -sub qqq { ## Un-pad paragraph of text. - local $_ = shift; - s{\n?^\s*}{ }mg; - return $_; -} + db_type => 'string', + hint => 'string', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + alphanum => { + db_type => 'string', + hint => 'alphanumeric', + pattern => qr|^([a-zA-Z0-9-_]+)$|, + format => q( $1 ) + }, + text => { + db_type => 'text', + hint => 'free text', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + boolean => { + db_type => 'boolean', + hint => 'yes|no', + pattern => qr|^([yn])|i, + check => q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : 'no' ) + }, + integer => { + db_type => 'integer', + hint => 'integer', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + decimal => { + db_type => 'decimal', + hint => 'decimal', + pattern => qr|^(\d+(?:\.\d+)?)$|, + format => q( $1 ) + }, + hexadecimal => { + db_type => 'hexadecimal', + hint => 'hexadecimal', + pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, + format => q( '0x'.$1 ) + }, + tristate => { + db_type => 'string', + hint => 'auto|yes|no', + pattern => qr|^([ayn])|i, check=>q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : ($1 =~ /^n/ ? 'no' : 'auto' ) ) + }, + abs_path => { + db_type => 'string', + hint => '/absolute/path/to/somewhere', + pattern => qr|^((?:/[^/]*)+?)/?$|, + format => q( $1 ) + }, + rel_path => { + db_type => 'string', + hint => 'relative/path/to/somewhere', + pattern => qr|^((?:[^/].*)?)/?$|, + format => q( $1 ) + }, + directory => { + db_type => 'string', + hint => 'directory', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + file => { + db_type => 'string', + hint => 'filename', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + hostname => { + db_type => 'string', + hint => 'host.your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)$|, + format => q( $1 ) + }, + url => { + db_type => 'string', + hint => 'http://host.your.domain/', + pattern => qr|^(?:http://)?(.+)$|, + format => q( 'http://'.$1 ) + }, + email => { + db_type => 'string', + hint => 'your.name@your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, + format => q( $1\@$2 ) + }, +); our @options = ( { - name => "ZM_SKIN_DEFAULT", - default => "classic", - description => "Default skin used by web interface", + name => 'ZM_SKIN_DEFAULT', + default => 'classic', + description => 'Default skin used by web interface', - help => qqq(" + help => q` ZoneMinder allows the use of many different web interfaces. This option allows you to set the default skin used by the website. Users can change their skin later, this merely sets the default. - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_CSS_DEFAULT", - default => "classic", - description => "Default set of css files used by web interface", - help => qqq(" + name => 'ZM_CSS_DEFAULT', + default => 'classic', + description => 'Default set of css files used by web interface', + help => q` ZoneMinder allows the use of many different web interfaces, and some skins allow the use of different set of CSS files to control the appearance. This option allows you to set the default set of css files used by the website. Users can change their css later, this merely sets the default. - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_LANG_DEFAULT", - default => "en_gb", - description => "Default language used by web interface", - help => qqq(" + name => 'ZM_LANG_DEFAULT', + default => 'en_gb', + description => 'Default language used by web interface', + help => q` ZoneMinder allows the web interface to use languages other than English if the appropriate language file has been created and is present. This option allows you to change the default language that is used from the shipped language, British English, to another language - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_USE_AUTH", - default => "no", - description => "Authenticate user logins to ZoneMinder", - help => qqq(" + name => 'ZM_OPT_USE_AUTH', + default => 'no', + description => 'Authenticate user logins to ZoneMinder', + help => q` ZoneMinder can run in two modes. The simplest is an entirely unauthenticated mode where anyone can access ZoneMinder and perform all tasks. This is most suitable for installations @@ -225,15 +219,15 @@ our @options = ( mode enables user accounts with varying sets of permissions. Users must login or authenticate to access ZoneMinder and are limited by their defined permissions. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_TYPE", - default => "builtin", - description => "What is used to authenticate ZoneMinder users", - help => qqq(" + name => 'ZM_AUTH_TYPE', + default => 'builtin', + description => 'What is used to authenticate ZoneMinder users', + help => q` ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and @@ -243,21 +237,21 @@ our @options = ( 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured ion ZoneMinder. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { - db_type => "string", - hint => "builtin|remote", + db_type => 'string', + hint => 'builtin|remote', pattern => qr|^([br])|i, - format => q( $1 =~ /^b/ ? "builtin" : "remote" ) + format => q( $1 =~ /^b/ ? 'builtin' : 'remote' ) }, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_RELAY", - default => "hashed", - description => "Method used to relay authentication information", - help => qqq(" + name => 'ZM_AUTH_RELAY', + default => 'hashed', + description => 'Method used to relay authentication information', + help => q` When ZoneMinder is running in authenticated mode it can pass user details between the web pages and the back end processes. There are two methods for doing this. This first is to use a @@ -268,39 +262,39 @@ our @options = ( your system or you have a completely isolated system with no external access. You can also switch off authentication relaying if your system is isolated in other ways. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { - db_type => "string", - hint => "hashed|plain|none", + db_type => 'string', + hint => 'hashed|plain|none', pattern => qr|^([hpn])|i, - format => q( ($1 =~ /^h/) ? "hashed" : ($1 =~ /^p/ ? "plain" : "none" ) ) + format => q( ($1 =~ /^h/) ? 'hashed' : ($1 =~ /^p/ ? 'plain' : 'none' ) ) }, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_SECRET", - default => "...Change me to something unique...", - description => "Secret for encoding hashed authentication information", - help => qqq(" + name => 'ZM_AUTH_HASH_SECRET', + default => '...Change me to something unique...', + description => 'Secret for encoding hashed authentication information', + help => q` When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and password. Although these string are reasonably secure the addition of a random secret increases security substantially. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_IPS", - default => "yes", - description => "Include IP addresses in the authentication hash", - help => qqq(" + name => 'ZM_AUTH_HASH_IPS', + default => 'yes', + description => 'Include IP addresses in the authentication hash', + help => q` When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests @@ -311,19 +305,19 @@ our @options = ( whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_LOGINS", - default => "no", - description => "Allow login by authentication hash", - help => qqq(" + name => 'ZM_AUTH_HASH_LOGINS', + default => 'no', + description => 'Allow login by authentication hash', + help => q` The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for @@ -337,90 +331,107 @@ our @options = ( authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_USE_API", - default => "yes", - description => "Enable ZoneMinder APIs", - help => qqq(" + name => 'ZM_ENABLE_CSRF_MAGIC', + default => 'no', + description => 'Enable csrf-magic library', + help => q` + CSRF stands for Cross-Site Request Forgery which, under specific + circumstances, can allow an attacker to perform any task your + ZoneMinder user account has permission to perform. To accomplish + this, the attacker must write a very specific web page and get + you to navigate to it, while you are logged into the ZoneMinder + web console at the same time. Enabling ZM_ENABLE_CSRF_MAGIC will + help mitigate these kinds of attackes. Be warned this feature + is experimental and may cause problems, particularly with the API. + If you find a false positive and can document how to reproduce it, + then please report it. This feature defaults to OFF currently due to + its experimental nature. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_USE_API', + default => 'yes', + description => 'Enable ZoneMinder APIs', + help => q` ZoneMinder now features a new API using which 3rd party - applications can interact with ZoneMinder data. It is + applications can interact with ZoneMinder data. It is STRONGLY recommended that you enable authentication along with APIs. Note that the APIs return sensitive data like Monitor access details which are configured as JSON objects. Which is why we recommend you enabling authentication, especially if you are exposing your ZM instance on the Internet. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, # PP - Google reCaptcha settings { - name => "ZM_OPT_USE_GOOG_RECAPTCHA", - default => "no", - description => "Add Google reCaptcha to login page", - help => qqq(" - This option allows you to include a google + name => 'ZM_OPT_USE_GOOG_RECAPTCHA', + default => 'no', + description => 'Add Google reCaptcha to login page', + help => q` + This option allows you to include a google reCaptcha validation at login. This means in addition to providing - a valid usernane and password, you will also have to + a valid usernane and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reach out to google servers for captcha validation. Also please note that enabling this option will break 3rd party clients like zmNinja and zmView as they also need to login to ZoneMinder and they will fail the reCaptcha test. - "), + `, requires => [ - {name=>"ZM_OPT_USE_AUTH", value=>"yes"} + {name=>'ZM_OPT_USE_AUTH', value=>'yes'} ], type => $types{boolean}, - category => "system", + category => 'system', }, - { - name => "ZM_OPT_GOOG_RECAPTCHA_SITEKEY", - default => "...Insert your recaptcha site-key here...", - description => "Your recaptcha site-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ + name => 'ZM_OPT_GOOG_RECAPTCHA_SITEKEY', + default => '...Insert your recaptcha site-key here...', + description => 'Your recaptcha site-key', + help => q`You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ for more details. - "), + `, requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} + {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_GOOG_RECAPTCHA_SECRETKEY", - default => "...Insert your recaptcha secret-key here...", - description => "Your recaptcha secret-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ + name => 'ZM_OPT_GOOG_RECAPTCHA_SECRETKEY', + default => '...Insert your recaptcha secret-key here...', + description => 'Your recaptcha secret-key', + help => q`You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ for more details. - "), + `, requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} + {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} ], type => $types{string}, - category => "system", + category => 'system', }, - - { - name => "ZM_DIR_EVENTS", - default => "events", - description => "Directory where events are stored", - help => qqq(" + name => 'ZM_DIR_EVENTS', + default => 'events', + description => 'Directory where events are stored', + help => q` This is the path to the events directory where all the event images and other miscellaneous files are stored. CAUTION: The directory you specify here cannot be outside the web root. This @@ -429,15 +440,15 @@ our @options = ( share, then you should mount the drive or share directly to the ZoneMinder events folder or follow the instructions in the ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_USE_DEEP_STORAGE", - default => "yes", - description => "Use a deep filesystem hierarchy for events", - help => qqq(" + name => 'ZM_USE_DEEP_STORAGE', + default => 'yes', + description => 'Use a deep filesystem hierarchy for events', + help => q` This option is now the default for new ZoneMinder systems and should not be changed. Previous versions of ZoneMinder stored all events for a monitor under one folder. Enabling @@ -447,18 +458,18 @@ our @options = ( than 32k files in a single folder inherent in some filesystems. It is important to note that you cannot simply change this option. You must stop zoneminder, enable USE_DEEP_STORAGE, and - then run \"sudo zmupdate.pl --migrate-events\". FAILURE TO DO + then run "sudo zmupdate.pl --migrate-events". FAILURE TO DO SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder WiKi for further details. - "), + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_DIR_IMAGES", - default => "images", - description => "Directory where the images that the ZoneMinder client generates are stored", - help => qqq(" + name => 'ZM_DIR_IMAGES', + default => 'images', + description => 'Directory where the images that the ZoneMinder client generates are stored', + help => q` ZoneMinder generates a myriad of images, mostly of which are associated with events. For those that aren't this is where they go. CAUTION: The directory you specify here cannot be @@ -468,28 +479,42 @@ our @options = ( or share directly to the ZoneMinder images folder or follow the instructions in the ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_DIR_SOUNDS", - default => "sounds", - description => "Directory to the sounds that the ZoneMinder client can use", - help => qqq(" + name => 'ZM_DIR_SOUNDS', + default => 'sounds', + description => 'Directory to the sounds that the ZoneMinder client can use', + help => q` ZoneMinder can optionally play a sound file when an alarm is detected. This indicates where to look for this file. CAUTION: The directory you specify here cannot be outside the web root. Most users should never change this value. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_ZMS", - default => "/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" + name => 'ZM_DIR_EXPORTS', + default => '@ZM_TMPDIR@', + description => 'Directory where exported archives are stored', + help => q` + This is the path to the exports directory where exported + tar.gz and zip archives are stored. By default this points to + ZoneMinder's temp folder, which often sits in ram. Since exported + archives can potentially become large, it is a good idea to move + this folder to some other location on machines with low memory. + `, + type => $types{directory}, + category => 'paths', + }, + { + name => 'ZM_PATH_ZMS', + default => '/cgi-bin/nph-zms', + description => 'Web path to zms streaming server', + help => q` The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what @@ -498,15 +523,15 @@ our @options = ( parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'. - "), + `, type => $types{rel_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_COLOUR_JPEG_FILES", - default => "no", - description => "Colourise greyscale JPEG files", - help => qqq(" + name => 'ZM_COLOUR_JPEG_FILES', + default => 'no', + description => 'Colourise greyscale JPEG files', + help => q` Cameras that capture in greyscale can write their captured images to jpeg files with a corresponding greyscale colour space. This saves a small amount of disk space over colour @@ -514,15 +539,15 @@ our @options = ( with this colour space or have to convert it beforehand. Setting this option to yes uses up a little more space but makes creation of MPEG files much faster. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_ADD_JPEG_COMMENTS", - default => "no", - description => "Add jpeg timestamp annotations as file header comments", - help => qqq(" + name => 'ZM_ADD_JPEG_COMMENTS', + default => 'no', + description => 'Add jpeg timestamp annotations as file header comments', + help => q` JPEG files may have a number of extra fields added to the file header. The comment field may have any kind of text added. This options allows you to have the same text that is used to @@ -530,15 +555,15 @@ our @options = ( comment. If you archive event images to other locations this may help you locate images for particular events or times if you use software that can read comment headers. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_JPEG_FILE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the saved event files (1-100)", - help => qqq(" + name => 'ZM_JPEG_FILE_QUALITY', + default => '70', + description => 'Set the JPEG quality setting for the saved event files (1-100)', + help => q` When ZoneMinder detects an event it will save the images associated with that event to files. These files are in the JPEG format and can be viewed or streamed later. This option @@ -551,15 +576,15 @@ our @options = ( except if the capture image has caused an alarm and the alarm file quality option is set at a higher value when that is used instead. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_JPEG_ALARM_FILE_QUALITY", - default => "0", - description => "Set the JPEG quality setting for the saved event files during an alarm (1-100)", - help => qqq(" + name => 'ZM_JPEG_ALARM_FILE_QUALITY', + default => '0', + description => 'Set the JPEG quality setting for the saved event files during an alarm (1-100)', + help => q` This value is equivalent to the regular jpeg file quality setting above except that it only applies to images saved while in an alarm state and then only if this value is set to a @@ -568,16 +593,16 @@ our @options = ( default of 0 effectively means to use the regular file quality setting for all saved images. This is to prevent acccidentally saving important images at a worse quality setting. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, # Deprecated, now stream quality { - name => "ZM_JPEG_IMAGE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" + name => 'ZM_JPEG_IMAGE_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be @@ -588,15 +613,15 @@ our @options = ( does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. - "), + `, type => $types{integer}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_JPEG_STREAM_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" + name => 'ZM_JPEG_STREAM_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be @@ -607,15 +632,15 @@ our @options = ( does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_TIMED_FRAMES", - default => "yes", - description => "Tag video frames with a timestamp for more realistic streaming", - help => qqq(" + name => 'ZM_MPEG_TIMED_FRAMES', + default => 'yes', + description => 'Tag video frames with a timestamp for more realistic streaming', + help => q` When using streamed MPEG based video, either for live monitor streams or events, ZoneMinder can send the streams in two ways. If this option is selected then the timestamp for each frame, @@ -626,15 +651,15 @@ our @options = ( calculated and that is used to schedule frames instead. This option should be selected unless you encounter problems with your preferred streaming method. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_LIVE_FORMAT", - default => "swf", - description => "What format 'live' video streams are played in", - help => qqq(" + name => 'ZM_MPEG_LIVE_FORMAT', + default => 'swf', + description => q`What format 'live' video streams are played in`, + help => q` When using MPEG mode ZoneMinder can output live video. However what formats are handled by the browser varies greatly between machines. This option allows you to specify a video format @@ -645,15 +670,15 @@ our @options = ( what, if anything, works on a Linux platform. If you find out please let me know! If this option is left blank then live streams will revert to being in motion jpeg format - "), + `, type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_REPLAY_FORMAT", - default => "swf", - description => "What format 'replay' video streams are played in", - help => qqq(" + name => 'ZM_MPEG_REPLAY_FORMAT', + default => 'swf', + description => q`What format 'replay' video streams are played in`, + help => q` When using MPEG mode ZoneMinder can replay events in encoded video format. However what formats are handled by the browser varies greatly between machines. This option allows you to @@ -664,27 +689,27 @@ our @options = ( or 'avi' etc should work under Linux. If you know any more then please let me know! If this option is left blank then live streams will revert to being in motion jpeg format - "), + `, type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_RAND_STREAM", - default => "yes", - description => "Add a random string to prevent caching of streams", - help => qqq(" + name => 'ZM_RAND_STREAM', + default => 'yes', + description => 'Add a random string to prevent caching of streams', + help => q` Some browsers can cache the streams used by ZoneMinder. In order to prevent his a harmless random string can be appended to the url to make each invocation of the stream appear unique. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_OPT_CAMBOZOLA", - default => "no", - description => "Is the (optional) cambozola java streaming client installed", - help => qqq(" + name => 'ZM_OPT_CAMBOZOLA', + default => 'no', + description => 'Is the (optional) cambozola java streaming client installed', + help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If @@ -692,15 +717,15 @@ our @options = ( from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_PATH_CAMBOZOLA", - default => "cambozola.jar", - description => "Web path to (optional) cambozola java streaming client", - help => qqq(" + name => 'ZM_PATH_CAMBOZOLA', + default => 'cambozola.jar', + description => 'Web path to (optional) cambozola java streaming client', + help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If @@ -710,30 +735,30 @@ our @options = ( be viewed. Leave this as 'cambozola.jar' if cambozola is installed in the same directory as the ZoneMinder web client files. - "), - requires => [ { name=>"ZM_OPT_CAMBOZOLA", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_CAMBOZOLA', value=>'yes' } ], type => $types{rel_path}, - category => "images", + category => 'images', }, { - name => "ZM_RELOAD_CAMBOZOLA", - default => "0", - description => "After how many seconds should Cambozola be reloaded in live view", - help => qqq(" + name => 'ZM_RELOAD_CAMBOZOLA', + default => '0', + description => 'After how many seconds should Cambozola be reloaded in live view', + help => q` Cambozola allows for the viewing of streaming MJPEG however it caches the entire stream into cache space on the computer, setting this to a number > 0 will cause it to automatically reload after that many seconds to avoid filling up a hard drive. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_TIMESTAMP_ON_CAPTURE", - default => "yes", - description => "Timestamp images as soon as they are captured", - help => qqq(" + name => 'ZM_TIMESTAMP_ON_CAPTURE', + default => 'yes', + description => 'Timestamp images as soon as they are captured', + help => q` ZoneMinder can add a timestamp to images in two ways. The default method, when this option is set, is that each image is timestamped immediately when captured and so the image held in @@ -750,30 +775,30 @@ our @options = ( desirable side effect that the timestamp is always applied at the same resolution so an image that has scaling applied will still have a legible and correctly scaled timestamp. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_CPU_EXTENSIONS", - default => "yes", - description => "Use advanced CPU extensions to increase performance", - help => qqq(" + name => 'ZM_CPU_EXTENSIONS', + default => 'yes', + description => 'Use advanced CPU extensions to increase performance', + help => q` When advanced processor extensions such as SSE2 or SSSE3 are available, ZoneMinder can use them, which should increase performance and reduce system load. Enabling this option on processors that do not support the advanced processors extensions used by ZoneMinder is harmless and will have no effect. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_FAST_IMAGE_BLENDS", - default => "yes", - description => "Use a fast algorithm to blend the reference image", - help => qqq(" + name => 'ZM_FAST_IMAGE_BLENDS', + default => 'yes', + description => 'Use a fast algorithm to blend the reference image', + help => q` To detect alarms ZoneMinder needs to blend the captured image with the stored reference image to update it for comparison with the next image. The reference blend percentage specified @@ -786,15 +811,15 @@ our @options = ( Any other blend percentage will be rounded to the nearest possible one. The alternative is to switch this option off and use standard blending instead, which is slower. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_OPT_ADAPTIVE_SKIP", - default => "yes", - description => "Should frame analysis try and be efficient in skipping frames", - help => qqq(" + name => 'ZM_OPT_ADAPTIVE_SKIP', + default => 'yes', + description => 'Should frame analysis try and be efficient in skipping frames', + help => q` In previous versions of ZoneMinder the analysis daemon would attempt to keep up with the capture daemon by processing the last captured frame on each pass. This would sometimes have the @@ -817,15 +842,15 @@ our @options = ( adaptive algorithm to be overwhelmed and not have time to react to a rapid build up of pending frames and thus for a buffer overrun condition to occur. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_MAX_SUSPEND_TIME", - default => "30", - description => "Maximum time that a monitor may have motion detection suspended", - help => qqq(" + name => 'ZM_MAX_SUSPEND_TIME', + default => '30', + description => 'Maximum time that a monitor may have motion detection suspended', + help => q` ZoneMinder allows monitors to have motion detection to be suspended, for instance while panning a camera. Ordinarily this relies on the operator resuming motion detection afterwards as @@ -835,29 +860,29 @@ our @options = ( motion detection. This time can be extended by subsequent suspend indications after the first so continuous camera movement will also occur while the monitor is suspended. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, # Deprecated, really no longer necessary { - name => "ZM_OPT_REMOTE_CAMERAS", - default => "no", - description => "Are you going to use remote/networked cameras", - help => qqq(" + name => 'ZM_OPT_REMOTE_CAMERAS', + default => 'no', + description => 'Are you going to use remote/networked cameras', + help => q` ZoneMinder can work with both local cameras, ie. those attached physically to your computer and remote or network cameras. If you will be using networked cameras select this option. - "), + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, # Deprecated, now set on a per monitor basis using the Method field { - name => "ZM_NETCAM_REGEXPS", - default => "yes", - description => "Use regular expression matching with network cameras", - help => qqq(" + name => 'ZM_NETCAM_REGEXPS', + default => 'yes', + description => 'Use regular expression matching with network cameras', + help => q` Traditionally ZoneMinder has used complex regular regular expressions to handle the multitude of formats that network cameras produce. In versions from 1.21.1 the default is to use @@ -866,64 +891,64 @@ our @options = ( problems you can try the older, but more flexible, regular expression based method by selecting this option. Note, to use this method you must have libpcre installed on your system. - "), - requires => [ { name => "ZM_OPT_REMOTE_CAMERAS", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_REMOTE_CAMERAS', value => 'yes' } ], type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_HTTP_VERSION", - default => "1.0", - description => "The version of HTTP that ZoneMinder will use to connect", - help => qqq(" + name => 'ZM_HTTP_VERSION', + default => '1.0', + description => 'The version of HTTP that ZoneMinder will use to connect', + help => q` ZoneMinder can communicate with network cameras using either of the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall back to the version it supports with no problem so this should usually by left at the default. However it can be changed to HTTP/1.0 if necessary to resolve particular issues. - "), + `, type => { - db_type => "string", - hint => "1.1|1.0", + db_type => 'string', + hint => '1.1|1.0', pattern => qr|^(1\.[01])$|, - format => q( $1?$1:"" ) + format => q( $1?$1:'' ) }, - category => "network", + category => 'network', }, { - name => "ZM_HTTP_UA", - default => "ZoneMinder", - description => "The user agent that ZoneMinder uses to identify itself", - help => qqq(" + name => 'ZM_HTTP_UA', + default => 'ZoneMinder', + description => 'The user agent that ZoneMinder uses to identify itself', + help => q` When ZoneMinder communicates with remote cameras it will identify itself using this string and it's version number. This is normally sufficient, however if a particular cameras expects only to communicate with certain browsers then this can be changed to a different string identifying ZoneMinder as Internet Explorer or Netscape etc. - "), + `, type => $types{string}, - category => "network", + category => 'network', }, { - name => "ZM_HTTP_TIMEOUT", - default => "2500", - description => "How long ZoneMinder waits before giving up on images (milliseconds)", - help => qqq(" + name => 'ZM_HTTP_TIMEOUT', + default => '2500', + description => 'How long ZoneMinder waits before giving up on images (milliseconds)', + help => q` When retrieving remote images ZoneMinder will wait for this length of time before deciding that an image is not going to arrive and taking steps to retry. This timeout is in milliseconds (1000 per second) and will apply to each part of an image if it is not sent in one whole chunk. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_MIN_RTP_PORT", - default => "40200", - description => "Minimum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" + name => 'ZM_MIN_RTP_PORT', + default => '40200', + description => 'Minimum port that ZoneMinder will listen for RTP traffic on', + help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This @@ -933,15 +958,15 @@ our @options = ( should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_MAX_RTP_PORT", - default => "40499", - description => "Maximum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" + name => 'ZM_MAX_RTP_PORT', + default => '40499', + description => 'Maximum port that ZoneMinder will listen for RTP traffic on', + help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This @@ -953,54 +978,54 @@ our @options = ( wish to use unicasting. You should also ensure that you have opened up at least two ports for each monitor that will be connecting to unicasting network cameras. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_OPT_FFMPEG", - default => "@OPT_FFMPEG@", - description => "Is the ffmpeg video encoder/decoder installed", - help => qqq(" + name => 'ZM_OPT_FFMPEG', + default => '@OPT_FFMPEG@', + description => 'Is the ffmpeg video encoder/decoder installed', + help => q` ZoneMinder can optionally encode a series of video images into an MPEG encoded movie file for viewing, downloading or storage. This option allows you to specify whether you have the ffmpeg tools installed. Note that creating MPEG files can be fairly CPU and disk intensive and is not a required option as events can still be reviewed as video streams without it. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_PATH_FFMPEG", - default => "@PATH_FFMPEG@", - description => "Path to (optional) ffmpeg mpeg encoder", - help => "This path should point to where ffmpeg has been installed.", - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + name => 'ZM_PATH_FFMPEG', + default => '@PATH_FFMPEG@', + description => 'Path to (optional) ffmpeg mpeg encoder', + help => 'This path should point to where ffmpeg has been installed.', + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{abs_path}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_INPUT_OPTIONS", - default => "", - description => "Additional input options to ffmpeg", - help => qqq(" + name => 'ZM_FFMPEG_INPUT_OPTIONS', + default => '', + description => 'Additional input options to ffmpeg', + help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the input to ffmpeg (options that are given before the -i option). Check the ffmpeg documentation for a full list of options which may be used here. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_OUTPUT_OPTIONS", - default => "-r 25", - description => "Additional output options to ffmpeg", - help => qqq(" + name => 'ZM_FFMPEG_OUTPUT_OPTIONS', + default => '-r 25', + description => 'Additional output options to ffmpeg', + help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the output from ffmpeg (options that @@ -1008,16 +1033,16 @@ our @options = ( for a full list of options which may be used here. The most common one will often be to force an output frame rate supported by the video encoder. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_FORMATS", - default => "mpg mpeg wmv asf avi* mov swf 3gp**", - description => "Formats to allow for ffmpeg video generation", - help => qqq(" + name => 'ZM_FFMPEG_FORMATS', + default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + description => 'Formats to allow for ffmpeg video generation', + help => q` Ffmpeg can generate video in many different formats. This option allows you to list the ones you want to be able to select. As new formats are supported by ffmpeg you can add them @@ -1025,31 +1050,31 @@ our @options = ( format indicates that this will be the default format used for web video, adding '**' defines the default format for phone video. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_OPEN_TIMEOUT", - default => "10", - description => "Timeout in seconds when opening a stream.", - help => qqq(" + name => 'ZM_FFMPEG_OPEN_TIMEOUT', + default => '10', + description => 'Timeout in seconds when opening a stream.', + help => q` When Ffmpeg is opening a stream, it can take a long time before failing; certain circumstances even seem to be able to lock indefinitely. This option allows you to set a maximum time in seconds to pass before closing the stream and trying to reopen it again. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_LOG_LEVEL_SYSLOG", - default => "0", - description => "Save logging output to the system log", - help => qqq(" + name => 'ZM_LOG_LEVEL_SYSLOG', + default => '0', + description => 'Save logging output to the system log', + help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option @@ -1063,20 +1088,20 @@ our @options = ( should use caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below - "), + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_FILE", - default => "-5", - description => "Save logging output to component files", - help => qqq(" + name => 'ZM_LOG_LEVEL_FILE', + default => '-5', + description => 'Save logging output to component files', + help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option @@ -1093,20 +1118,20 @@ our @options = ( performance though file output has less impact than the other options. If you want debug you will also need to set a level and component below - "), + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_WEBLOG", - default => "-5", - description => "Save logging output to the weblog", - help => qqq(" + name => 'ZM_LOG_LEVEL_WEBLOG', + default => '-5', + description => 'Save logging output to the weblog', + help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option @@ -1120,20 +1145,20 @@ our @options = ( should use caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below - "), + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_DATABASE", - default => "0", - description => "Save logging output to the database", - help => qqq(" + name => 'ZM_LOG_LEVEL_DATABASE', + default => '0', + description => 'Save logging output to the database', + help => q` ZoneMinder logging is now more more integrated between components and allows you to specify the destination for logging output and the individual levels for each. This option @@ -1151,20 +1176,20 @@ our @options = ( caution when setting this option to Debug as it can affect severely affect system performance. If you want debug you will also need to set a level and component below - "), + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DATABASE_LIMIT", - default => "7 day", - description => "Maximum number of log entries to retain", - help => qqq(" + name => 'ZM_LOG_DATABASE_LIMIT', + default => '7 day', + description => 'Maximum number of log entries to retain', + help => q` If you are using database logging then it is possible to quickly build up a large number of entries in the Logs table. This option allows you to specify how many of these entries are @@ -1178,15 +1203,15 @@ our @options = ( end). The Logs table is pruned periodically so it is possible for more than the expected number of rows to be present briefly in the meantime. - "), + `, type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG", - default => "no", - description => "Switch debugging on", - help => qqq(" + name => 'ZM_LOG_DEBUG', + default => 'no', + description => 'Switch debugging on', + help => q` ZoneMinder components usually support debug logging available to help with diagnosing problems. Binary components have several levels of debug whereas more other components have only @@ -1195,15 +1220,15 @@ our @options = ( you switch on other options that allow you to configure additional debug information to be output. Components will pick up this instruction when they are restarted. - "), + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_TARGET", - default => "", - description => "What components should have extra debug enabled", - help => qqq(" + name => 'ZM_LOG_DEBUG_TARGET', + default => '', + description => 'What components should have extra debug enabled', + help => q` There are three scopes of debug available. Leaving this option blank means that all components will use extra debug (not recommended). Setting this option to '_', e.g. _zmc, @@ -1214,16 +1239,16 @@ our @options = ( their names without the .pl extension, e.g. '_zmvideo' and to debug issues with the web interface use '_web'. You can specify multiple targets by separating them with '|' characters. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_LEVEL", + name => 'ZM_LOG_DEBUG_LEVEL', default => 1, - description => "What level of extra debug should be enabled", - help => qqq(" + description => 'What level of extra debug should be enabled', + help => q` There are 9 levels of debug available, with higher numbers being more debug and level 0 being no debug. However not all levels are used by all components. Also if there is debug at a @@ -1233,21 +1258,21 @@ our @options = ( of debug you wish to see is present. Scripts and the web interface only have one level so this is an on/off type option for them. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => { - db_type => "integer", - hint => "1|2|3|4|5|6|7|8|9", + db_type => 'integer', + hint => '1|2|3|4|5|6|7|8|9', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_FILE", - default => "@ZM_LOGDIR@/zm_debug.log+", - description => "Where extra debug is output to", - help => qqq(" + name => 'ZM_LOG_DEBUG_FILE', + default => '@ZM_LOGDIR@/zm_debug.log+', + description => 'Where extra debug is output to', + help => q` This option allows you to specify a different target for debug output. All components have a default log file which will norally be in /tmp or /var/log and this is where debug will be @@ -1263,31 +1288,31 @@ our @options = ( overwriting the same log. You should ensure that permissions are set up to allow writing to the file and directory specified here. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_CHECK_PERIOD", - default => "900", - description => "Time period used when calculating overall system health", - help => qqq(" + name => 'ZM_LOG_CHECK_PERIOD', + default => '900', + description => 'Time period used when calculating overall system health', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. This option allows you to indicate what period of historical events are used in this calculation. This value is expressed in seconds and is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_WAR_COUNT", - default => "1", - description => "Number of warnings indicating system alert state", - help => qqq(" + name => 'ZM_LOG_ALERT_WAR_COUNT', + default => '1', + description => 'Number of warnings indicating system alert state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1296,15 +1321,15 @@ our @options = ( system alert state. A value of zero means warnings are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_ERR_COUNT", - default => "1", - description => "Number of errors indicating system alert state", - help => qqq(" + name => 'ZM_LOG_ALERT_ERR_COUNT', + default => '1', + description => 'Number of errors indicating system alert state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1313,15 +1338,15 @@ our @options = ( system alert state. A value of zero means errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_FAT_COUNT", - default => "0", - description => "Number of fatal error indicating system alert state", - help => qqq(" + name => 'ZM_LOG_ALERT_FAT_COUNT', + default => '0', + description => 'Number of fatal error indicating system alert state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1330,15 +1355,15 @@ our @options = ( period to generate an overall system alert state. A value of zero means fatal errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_WAR_COUNT", - default => "100", - description => "Number of warnings indicating system alarm state", - help => qqq(" + name => 'ZM_LOG_ALARM_WAR_COUNT', + default => '100', + description => 'Number of warnings indicating system alarm state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1347,15 +1372,15 @@ our @options = ( system alarm state. A value of zero means warnings are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_ERR_COUNT", - default => "10", - description => "Number of errors indicating system alarm state", - help => qqq(" + name => 'ZM_LOG_ALARM_ERR_COUNT', + default => '10', + description => 'Number of errors indicating system alarm state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1364,15 +1389,15 @@ our @options = ( system alarm state. A value of zero means errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_FAT_COUNT", - default => "1", - description => "Number of fatal error indicating system alarm state", - help => qqq(" + name => 'ZM_LOG_ALARM_FAT_COUNT', + default => '1', + description => 'Number of fatal error indicating system alarm state', + help => q` When ZoneMinder is logging events to the database it can retrospectively examine the number of warnings and errors that have occurred to calculate an overall state of system health. @@ -1381,15 +1406,15 @@ our @options = ( period to generate an overall system alarm state. A value of zero means fatal errors are not considered. This value is ignored if LOG_LEVEL_DATABASE is set to None. - "), + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_RECORD_EVENT_STATS", - default => "yes", - description => "Record event statistical information, switch off if too slow", - help => qqq(" + name => 'ZM_RECORD_EVENT_STATS', + default => 'yes', + description => 'Record event statistical information, switch off if too slow', + help => q` This version of ZoneMinder records detailed information about events in the Stats table. This can help in profiling what the optimum settings are for Zones though this is tricky at @@ -1399,15 +1424,15 @@ our @options = ( be collected now in readiness for this but if you are concerned about performance you can switch this off in which case no Stats information will be saved. - "), + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_RECORD_DIAG_IMAGES", - default => "no", - description => "Record intermediate alarm diagnostic images, can be very slow", - help => qqq(" + name => 'ZM_RECORD_DIAG_IMAGES', + default => 'no', + description => 'Record intermediate alarm diagnostic images, can be very slow', + help => q` In addition to recording event statistics you can also record the intermediate diagnostic images that display the results of the various checks and processing that occur when trying to @@ -1417,15 +1442,15 @@ our @options = ( performance. Only switch this setting on for debug or analysis purposes and remember to switch it off again once no longer required. - "), + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_DUMP_CORES", - default => "no", - description => "Create core files on unexpected process failure.", - help => qqq(" + name => 'ZM_DUMP_CORES', + default => 'no', + description => 'Create core files on unexpected process failure.', + help => q` When an unrecoverable error occurs in a ZoneMinder binary process is has traditionally been trapped and the details written to logs to aid in remote analysis. However in some @@ -1442,15 +1467,15 @@ our @options = ( on your system via the 'ulimit -c' command or other means otherwise no file will be created regardless of the value of this option. - "), + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_PATH_MAP", - default => "/dev/shm", - description => "Path to the mapped memory files that that ZoneMinder can use", - help => qqq(" + name => 'ZM_PATH_MAP', + default => '/dev/shm', + description => 'Path to the mapped memory files that that ZoneMinder can use', + help => q` ZoneMinder has historically used IPC shared memory for shared data between processes. This has it's advantages and limitations. This version of ZoneMinder can use an alternate @@ -1463,754 +1488,757 @@ our @options = ( files and for the best performance it should be a tmpfs file system or ramdisk otherwise disk access may render this method slower than the regular shared memory one. - "), + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_SOCKS", - default => "@ZM_SOCKDIR@", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => qqq(" - ZoneMinder generally uses Unix domain sockets where possible. - This reduces the need for port assignments and prevents - external applications from possibly compromising the daemons. - However each Unix socket requires a .sock file to be created. - This option indicates where those socket files go. - "), + name => 'ZM_PATH_SOCKS', + default => '@ZM_SOCKDIR@', + description => 'Path to the various Unix domain socket files that ZoneMinder uses', + help => q` + ZoneMinder generally uses Unix domain sockets where possible. + This reduces the need for port assignments and prevents + external applications from possibly compromising the daemons. + However each Unix socket requires a .sock file to be created. + This option indicates where those socket files go. + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_LOGS", - default => "@ZM_LOGDIR@", - description => "Path to the various logs that the ZoneMinder daemons generate", - help => qqq(" - There are various daemons that are used by ZoneMinder to - perform various tasks. Most generate helpful log files and this - is where they go. They can be deleted if not required for - debugging. - "), + name => 'ZM_PATH_LOGS', + default => '@ZM_LOGDIR@', + description => 'Path to the various logs that the ZoneMinder daemons generate', + help => q` + There are various daemons that are used by ZoneMinder to + perform various tasks. Most generate helpful log files and this + is where they go. They can be deleted if not required for + debugging. + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_SWAP", - default => "@ZM_TMPDIR@", - description => "Path to location for temporary swap images used in streaming", - help => qqq(" - Buffered playback requires temporary swap images to be stored - for each instance of the streaming daemons. This option - determines where these images will be stored. The images will - actually be stored in sub directories beneath this location and - will be automatically cleaned up after a period of time. - "), + name => 'ZM_PATH_SWAP', + default => '@ZM_TMPDIR@', + description => 'Path to location for temporary swap images used in streaming', + help => q` + Buffered playback requires temporary swap images to be stored + for each instance of the streaming daemons. This option + determines where these images will be stored. The images will + actually be stored in sub directories beneath this location and + will be automatically cleaned up after a period of time. + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_ARP", - default => "", - description => "Path to a supported ARP tool", - help => qqq(" - The camera probe function uses Address Resolution Protocol in - order to find known devices on the network. Optionally supply - the full path to \"ip neigh\", \"arp -a\", or any other tool on - your system that returns ip/mac address pairs. If this field is - left empty, ZoneMinder will search for the command \"arp\" and - attempt to use that. - "), + name => 'ZM_PATH_ARP', + default => '', + description => 'Path to a supported ARP tool', + help => q` + The camera probe function uses Address Resolution Protocol in + order to find known devices on the network. Optionally supply + the full path to \"ip neigh\", \"arp -a\", or any other tool on + your system that returns ip/mac address pairs. If this field is + left empty, ZoneMinder will search for the command \"arp\" and + attempt to use that. + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_WEB_TITLE_PREFIX", - default => "ZM", - description => "The title prefix displayed on each window", - help => qqq(" - 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. - "), + name => 'ZM_WEB_TITLE_PREFIX', + default => 'ZM', + description => 'The title prefix displayed on each window', + help => q` + 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. + `, type => $types{string}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_CONSOLE_BANNER", - default => "", - description => "Arbitrary text message near the top of the console", - help => qqq(" - Allows the administrator to place an arbitrary text message - near the top of the web console. This is useful for the developers - to display a message which indicates the running instance of - ZoneMinder is a development snapshot, but it can also be used for - any other purpose as well. - "), + name => 'ZM_WEB_CONSOLE_BANNER', + default => '', + description => 'Arbitrary text message near the top of the console', + help => q` + Allows the administrator to place an arbitrary text message + near the top of the web console. This is useful for the developers + to display a message which indicates the running instance of + ZoneMinder is a development snapshot, but it can also be used for + any other purpose as well. + `, type => $types{string}, - category => "web", + category => 'web', }, { name => 'ZM_WEB_EVENT_DISK_SPACE', - default => '', - description => "Whether to show disk space used by each event.", - help => qqq("Adds another column to the listing of events - showing the disk space used by the event. This will impart a small - overhead as it will call du on the event directory. In practice - this overhead is fairly small but may be noticeable on IO-constrained - systems. - "), - type => $types{string}, - category => "web", - }, - { - name => "ZM_WEB_RESIZE_CONSOLE", - default => "yes", - description => "Should the console window resize itself to fit", - help => qqq(" - Traditionally the main ZoneMinder web console window has - resized itself to shrink to a size small enough to list only - the monitors that are actually present. This is intended to - make the window more unobtrusize but may not be to everyones - tastes, especially if opened in a tab in browsers which support - this kind if layout. Switch this option off to have the console - window size left to the users preference - "), + default => 'no', + description => 'Whether to show disk space used by each event.', + help => q`Adds another column to the listing of events + showing the disk space used by the event. This will impart a small + overhead as it will call du on the event directory. In practice + this overhead is fairly small but may be noticeable on IO-constrained + systems. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_ID_ON_CONSOLE", - default => "no", - description => "Should the console list the monitor id", - help => qqq("Some find it useful to have the id always visible - on the console. This option will add a column listing it. - "), + name => 'ZM_WEB_RESIZE_CONSOLE', + default => 'yes', + description => 'Should the console window resize itself to fit', + help => q` + Traditionally the main ZoneMinder web console window has + resized itself to shrink to a size small enough to list only + the monitors that are actually present. This is intended to + make the window more unobtrusize but may not be to everyones + tastes, especially if opened in a tab in browsers which support + this kind if layout. Switch this option off to have the console + window size left to the users preference + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_POPUP_ON_ALARM", - default => "yes", - description => "Should the monitor window jump to the top if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to pop to the front if an alarm occurs when the - window is minimised or behind another window. This is most - useful if your monitors are over doors for example when they - can pop up if someone comes to the doorway. - "), + name => 'ZM_WEB_ID_ON_CONSOLE', + default => 'no', + description => 'Should the console list the monitor id', + help => q` + Some find it useful to have the id always visible + on the console. This option will add a column listing it. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_OPT_X10", - default => "no", - description => "Support interfacing with X10 devices", - help => qqq(" - If you have an X10 Home Automation setup in your home you can - use ZoneMinder to initiate or react to X10 signals if your - computer has the appropriate interface controller. This option - indicates whether X10 options will be available in the browser - client. - "), + name => 'ZM_WEB_POPUP_ON_ALARM', + default => 'yes', + description => 'Should the monitor window jump to the top if an alarm occurs', + help => q` + When viewing a live monitor stream you can specify whether you + want the window to pop to the front if an alarm occurs when the + window is minimised or behind another window. This is most + useful if your monitors are over doors for example when they + can pop up if someone comes to the doorway. + `, type => $types{boolean}, - category => "x10", + category => 'web', }, { - name => "ZM_X10_DEVICE", - default => "/dev/ttyS0", - description => "What device is your X10 controller connected on", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - If you have an X10 controller device (e.g. XM10U) connected to - your computer this option details which port it is connected on, - the default of /dev/ttyS0 maps to serial or com port 1. - "), + name => 'ZM_OPT_X10', + default => 'no', + description => 'Support interfacing with X10 devices', + help => q` + If you have an X10 Home Automation setup in your home you can + use ZoneMinder to initiate or react to X10 signals if your + computer has the appropriate interface controller. This option + indicates whether X10 options will be available in the browser + client. + `, + type => $types{boolean}, + category => 'x10', + }, + { + name => 'ZM_X10_DEVICE', + default => '/dev/ttyS0', + description => 'What device is your X10 controller connected on', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + If you have an X10 controller device (e.g. XM10U) connected to + your computer this option details which port it is connected on, + the default of /dev/ttyS0 maps to serial or com port 1. + `, type => $types{abs_path}, - category => "x10", + category => 'x10', }, { - name => "ZM_X10_HOUSE_CODE", - default => "A", - description => "What X10 house code should be used", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - X10 devices are grouped together by identifying them as all - belonging to one House Code. This option details what that is. - It should be a single letter between A and P. - "), - type => { db_type=>"string", hint=>"A-P", pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, - category => "x10", + name => 'ZM_X10_HOUSE_CODE', + default => 'A', + description => 'What X10 house code should be used', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + X10 devices are grouped together by identifying them as all + belonging to one House Code. This option details what that is. + It should be a single letter between A and P. + `, + type => { db_type=>'string', hint=>'A-P', pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, + category => 'x10', }, { - name => "ZM_X10_DB_RELOAD_INTERVAL", - default => "60", - description => "How often (in seconds) the X10 daemon reloads the monitors from the database", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - The zmx10 daemon periodically checks the database to find out - what X10 events trigger, or result from, alarms. This option - determines how frequently this check occurs, unless you change - this area frequently this can be a fairly large value. - "), + name => 'ZM_X10_DB_RELOAD_INTERVAL', + default => '60', + description => 'How often (in seconds) the X10 daemon reloads the monitors from the database', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + The zmx10 daemon periodically checks the database to find out + what X10 events trigger, or result from, alarms. This option + determines how frequently this check occurs, unless you change + this area frequently this can be a fairly large value. + `, type => $types{integer}, - category => "x10", + category => 'x10', }, { - name => "ZM_WEB_SOUND_ON_ALARM", - default => "no", - description => "Should the monitor window play a sound if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to play a sound to alert you if an alarm - occurs. - "), + name => 'ZM_WEB_SOUND_ON_ALARM', + default => 'no', + description => 'Should the monitor window play a sound if an alarm occurs', + help => q` + When viewing a live monitor stream you can specify whether you + want the window to play a sound to alert you if an alarm + occurs. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_ALARM_SOUND", - default => "", - description => "The sound to play on alarm, put this in the sounds directory", - help => qqq(" - You can specify a sound file to play if an alarm occurs whilst - you are watching a live monitor stream. So long as your browser - understands the format it does not need to be any particular - type. This file should be placed in the sounds directory - defined earlier. - "), + name => 'ZM_WEB_ALARM_SOUND', + default => '', + description => 'The sound to play on alarm, put this in the sounds directory', + help => q` + You can specify a sound file to play if an alarm occurs whilst + you are watching a live monitor stream. So long as your browser + understands the format it does not need to be any particular + type. This file should be placed in the sounds directory + defined earlier. + `, type => $types{file}, - requires => [ { name => "ZM_WEB_SOUND_ON_ALARM", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_SOUND_ON_ALARM', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_COMPACT_MONTAGE", - default => "no", - description => "Compact the montage view by removing extra detail", - help => qqq(" - The montage view shows the output of all of your active - monitors in one window. This include a small menu and status - information for each one. This can increase the web traffic and - make the window larger than may be desired. Setting this option - on removes all this extraneous information and just displays - the images. - "), + name => 'ZM_WEB_COMPACT_MONTAGE', + default => 'no', + description => 'Compact the montage view by removing extra detail', + help => q` + The montage view shows the output of all of your active + monitors in one window. This include a small menu and status + information for each one. This can increase the web traffic and + make the window larger than may be desired. Setting this option + on removes all this extraneous information and just displays + the images. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_OPT_FAST_DELETE", - default => "no", - description => "Delete only event database records for speed", - help => qqq(" - Normally an event created as the result of an alarm consists of - entries in one or more database tables plus the various files - associated with it. When deleting events in the browser it can - take a long time to remove all of this if your are trying to do - a lot of events at once. It is recommended that you set this - option which means that the browser client only deletes the key - entries in the events table, which means the events will no - longer appear in the listing, and leaves the zmaudit daemon to - clear up the rest later. Note that this feature is less relevant - with modern hardware. Recommend this feature be left off. - "), + name => 'ZM_OPT_FAST_DELETE', + default => 'no', + description => 'Delete only event database records for speed', + help => q` + Normally an event created as the result of an alarm consists of + entries in one or more database tables plus the various files + associated with it. When deleting events in the browser it can + take a long time to remove all of this if your are trying to do + a lot of events at once. It is recommended that you set this + option which means that the browser client only deletes the key + entries in the events table, which means the events will no + longer appear in the listing, and leaves the zmaudit daemon to + clear up the rest later. Note that this feature is less relevant + with modern hardware. Recommend this feature be left off. + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_STRICT_VIDEO_CONFIG", - default => "yes", - description => "Allow errors in setting video config to be fatal", - help => qqq(" - With some video devices errors can be reported in setting the - various video attributes when in fact the operation was - successful. Switching this option off will still allow these - errors to be reported but will not cause them to kill the video - capture daemon. Note however that doing this will cause all - errors to be ignored including those which are genuine and - which may cause the video capture to not function correctly. - Use this option with caution. - "), + name => 'ZM_STRICT_VIDEO_CONFIG', + default => 'yes', + description => 'Allow errors in setting video config to be fatal', + help => q` + With some video devices errors can be reported in setting the + various video attributes when in fact the operation was + successful. Switching this option off will still allow these + errors to be reported but will not cause them to kill the video + capture daemon. Note however that doing this will cause all + errors to be ignored including those which are genuine and + which may cause the video capture to not function correctly. + Use this option with caution. + `, type => $types{boolean}, - category => "config", + category => 'config', }, { name => 'ZM_LD_PRELOAD', default => '', description => "Path to library to preload before launching daemons", - help => qqq("Some older cameras require the use of the v4l1 compat - library. This setting allows the setting of the path - to the library, so that it can be loaded by zmdc.pl - before launching zmc."), + help => q` + Some older cameras require the use of the v4l1 compat + library. This setting allows the setting of the path + to the library, so that it can be loaded by zmdc.pl + before launching zmc. + `, type => $types{abs_path}, category => 'config', }, { - name => "ZM_SIGNAL_CHECK_POINTS", - default => "10", - description => "How many points in a captured image to check for signal loss", - help => qqq(" - For locally attached video cameras ZoneMinder can check for - signal loss by looking at a number of random points on each - captured image. If all of these points are set to the same - fixed colour then the camera is assumed to have lost signal. - When this happens any open events are closed and a short one - frame signal loss event is generated, as is another when the - signal returns. This option defines how many points on each - image to check. Note that this is a maximum, any points found - to not have the check colour will abort any further checks so - in most cases on a couple of points will actually be checked. - Network and file based cameras are never checked. - "), + name => 'ZM_SIGNAL_CHECK_POINTS', + default => '10', + description => 'How many points in a captured image to check for signal loss', + help => q` + For locally attached video cameras ZoneMinder can check for + signal loss by looking at a number of random points on each + captured image. If all of these points are set to the same + fixed colour then the camera is assumed to have lost signal. + When this happens any open events are closed and a short one + frame signal loss event is generated, as is another when the + signal returns. This option defines how many points on each + image to check. Note that this is a maximum, any points found + to not have the check colour will abort any further checks so + in most cases on a couple of points will actually be checked. + Network and file based cameras are never checked. + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_V4L_MULTI_BUFFER", - default => "yes", - description => "Use more than one buffer for Video 4 Linux devices", - help => qqq(" - Performance when using Video 4 Linux devices is usually best if - multiple buffers are used allowing the next image to be - captured while the previous one is being processed. If you have - multiple devices on a card sharing one input that requires - switching then this approach can sometimes cause frames from - one source to be mixed up with frames from another. Switching - this option off prevents multi buffering resulting in slower - but more stable image capture. This option is ignored for - non-local cameras or if only one input is present on a capture - chip. This option addresses a similar problem to the - ZM_CAPTURES_PER_FRAME option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), + name => 'ZM_V4L_MULTI_BUFFER', + default => 'yes', + description => 'Use more than one buffer for Video 4 Linux devices', + help => q` + Performance when using Video 4 Linux devices is usually best if + multiple buffers are used allowing the next image to be + captured while the previous one is being processed. If you have + multiple devices on a card sharing one input that requires + switching then this approach can sometimes cause frames from + one source to be mixed up with frames from another. Switching + this option off prevents multi buffering resulting in slower + but more stable image capture. This option is ignored for + non-local cameras or if only one input is present on a capture + chip. This option addresses a similar problem to the + ZM_CAPTURES_PER_FRAME option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + override them in each individual monitor on the source page. + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_CAPTURES_PER_FRAME", - default => "1", - description => "How many images are captured per returned frame, for shared local cameras", - help => qqq(" - If you are using cameras attached to a video capture card which - forces multiple inputs to share one capture chip, it can - sometimes produce images with interlaced frames reversed - resulting in poor image quality and a distinctive comb edge - appearance. Increasing this setting allows you to force - additional image captures before one is selected as the - captured frame. This allows the capture hardware to 'settle - down' and produce better quality images at the price of lesser - capture rates. This option has no effect on (a) network - cameras, or (b) where multiple inputs do not share a capture - chip. This option addresses a similar problem to the - ZM_V4L_MULTI_BUFFER option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), + name => 'ZM_CAPTURES_PER_FRAME', + default => '1', + description => 'How many images are captured per returned frame, for shared local cameras', + help => q` + If you are using cameras attached to a video capture card which + forces multiple inputs to share one capture chip, it can + sometimes produce images with interlaced frames reversed + resulting in poor image quality and a distinctive comb edge + appearance. Increasing this setting allows you to force + additional image captures before one is selected as the + captured frame. This allows the capture hardware to 'settle + down' and produce better quality images at the price of lesser + capture rates. This option has no effect on (a) network + cameras, or (b) where multiple inputs do not share a capture + chip. This option addresses a similar problem to the + ZM_V4L_MULTI_BUFFER option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + override them in each individual monitor on the source page. + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_FILTER_RELOAD_DELAY", - default => "300", - description => "How often (in seconds) filters are reloaded in zmfilter", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are reloaded from the database to get the - latest versions or new filters. If you don't change filters - very often this value can be set to a large value. - "), + name => 'ZM_FILTER_RELOAD_DELAY', + default => '300', + description => 'How often (in seconds) filters are reloaded in zmfilter', + help => q` + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are reloaded from the database to get the + latest versions or new filters. If you don't change filters + very often this value can be set to a large value. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_FILTER_EXECUTE_INTERVAL", - default => "60", - description => "How often (in seconds) to run automatic saved filters", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are executed on the saved event in the - database. If you want a rapid response to new events this - should be a smaller value, however this may increase the - overall load on the system and affect performance of other - elements. - "), + name => 'ZM_FILTER_EXECUTE_INTERVAL', + default => '60', + description => 'How often (in seconds) to run automatic saved filters', + help => q` + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are executed on the saved event in the + database. If you want a rapid response to new events this + should be a smaller value, however this may increase the + overall load on the system and affect performance of other + elements. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_UPLOAD", - default => "no", - description => "Should ZoneMinder support uploading events from filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should be uploaded to a - remote server for archiving. This option specifies whether this - functionality should be available - "), + name => 'ZM_OPT_UPLOAD', + default => 'no', + description => 'Should ZoneMinder support uploading events from filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should be uploaded to a + remote server for archiving. This option specifies whether this + functionality should be available + `, type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_FORMAT", - default => "tar", - description => "What format the uploaded events should be created in.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - Uploaded events may be stored in either .tar or .zip format, - this option specifies which. Note that to use this you will - need to have the Archive::Tar and/or Archive::Zip perl modules - installed. - "), + name => 'ZM_UPLOAD_ARCH_FORMAT', + default => 'tar', + description => 'What format the uploaded events should be created in.', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + Uploaded events may be stored in either .tar or .zip format, + this option specifies which. Note that to use this you will + need to have the Archive::Tar and/or Archive::Zip perl modules + installed. + `, type => { - db_type =>"string", - hint =>"tar|zip", + db_type =>'string', + hint =>'tar|zip', pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^t/ ? "tar" : "zip" ) + format =>q( $1 =~ /^t/ ? 'tar' : 'zip' ) }, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_COMPRESS", - default => "no", - description => "Should archive files be compressed", - help => qqq(" - When the archive files are created they can be compressed. - However in general since the images are compressed already this - saves only a minimal amount of space versus utilising more CPU - in their creation. Only enable if you have CPU to waste and are - limited in disk space on your remote server or bandwidth. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_ARCH_COMPRESS', + default => 'no', + description => 'Should archive files be compressed', + help => q` + When the archive files are created they can be compressed. + However in general since the images are compressed already this + saves only a minimal amount of space versus utilising more CPU + in their creation. Only enable if you have CPU to waste and are + limited in disk space on your remote server or bandwidth. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_ANALYSE", - default => "no", - description => "Include the analysis files in the archive", - help => qqq(" - When the archive files are created they can contain either just - the captured frames or both the captured frames and, for frames - that caused an alarm, the analysed image with the changed area - highlighted. This option controls files are included. Only - include analysed frames if you have a high bandwidth connection - to the remote server or if you need help in figuring out what - caused an alarm in the first place as archives with these files - in can be considerably larger. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_ARCH_ANALYSE', + default => 'no', + description => 'Include the analysis files in the archive', + help => q` + When the archive files are created they can contain either just + the captured frames or both the captured frames and, for frames + that caused an alarm, the analysed image with the changed area + highlighted. This option controls files are included. Only + include analysed frames if you have a high bandwidth connection + to the remote server or if you need help in figuring out what + caused an alarm in the first place as archives with these files + in can be considerably larger. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_PROTOCOL", - default => "ftp", - description => "What protocol to use to upload events", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - ZoneMinder can upload events to a remote server using either - FTP or SFTP. Regular FTP is widely supported but not - necessarily very secure whereas SFTP (Secure FTP) runs over an - ssh connection and so is encrypted and uses regular ssh ports. - Note that to use this you will need to have the appropriate - perl module, either Net::FTP or Net::SFTP installed depending - on your choice. - "), + name => 'ZM_UPLOAD_PROTOCOL', + default => 'ftp', + description => 'What protocol to use to upload events', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + ZoneMinder can upload events to a remote server using either + FTP or SFTP. Regular FTP is widely supported but not + necessarily very secure whereas SFTP (Secure FTP) runs over an + ssh connection and so is encrypted and uses regular ssh ports. + Note that to use this you will need to have the appropriate + perl module, either Net::FTP or Net::SFTP installed depending + on your choice. + `, type => { - db_type =>"string", - hint =>"ftp|sftp", + db_type =>'string', + hint =>'ftp|sftp', pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^f/ ? "ftp" : "sftp" ) + format =>q( $1 =~ /^f/ ? 'ftp' : 'sftp' ) }, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_HOST", - default => "", - description => "The remote server to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the name, or ip - address, of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_HOST', + default => '', + description => 'The remote server to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the name, or ip + address, of the server to use. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_HOST", - default => "", - description => "The remote server to upload events to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the name, or ip address, - of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_HOST', + default => '', + description => 'The remote server to upload events to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the name, or ip address, + of the server to use. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_PORT", - default => "", - description => "The port on the remote upload server, if not the default (SFTP only)", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are using the SFTP protocol then this - option allows you to specify a particular port to use for - connection. If this option is left blank then the default, port - 22, is used. This option is ignored for FTP uploads. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_PORT', + default => '', + description => 'The port on the remote upload server, if not the default (SFTP only)', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are using the SFTP protocol then this + option allows you to specify a particular port to use for + connection. If this option is left blank then the default, port + 22, is used. This option is ignored for FTP uploads. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_USER", - default => "", - description => "Your ftp username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the username that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_USER', + default => '', + description => 'Your ftp username', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the username that + ZoneMinder should use to log in for ftp transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_USER", - default => "", - description => "Remote server username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the username that - ZoneMinder should use to log in for transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_USER', + default => '', + description => 'Remote server username', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the username that + ZoneMinder should use to log in for transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_PASS", - default => "", - description => "Your ftp password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the password that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_PASS', + default => '', + description => 'Your ftp password', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the password that + ZoneMinder should use to log in for ftp transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_PASS", - default => "", - description => "Remote server password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the password that - ZoneMinder should use to log in for transfer. If you are using - certificate based logins for SFTP servers you can leave this - option blank. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_PASS', + default => '', + description => 'Remote server password', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the password that + ZoneMinder should use to log in for transfer. If you are using + certificate based logins for SFTP servers you can leave this + option blank. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the local directory - that ZoneMinder should use for temporary upload files. These - are files that are created from events, uploaded and then - deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the local directory + that ZoneMinder should use for temporary upload files. These + are files that are created from events, uploaded and then + deleted. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the local directory that - ZoneMinder should use for temporary upload files. These are - files that are created from events, uploaded and then deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the local directory that + ZoneMinder should use for temporary upload files. These are + files that are created from events, uploaded and then deleted. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the maximum ftp - inactivity timeout (in seconds) that should be tolerated before - ZoneMinder determines that the transfer has failed and closes - down the connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the maximum ftp + inactivity timeout (in seconds) that should be tolerated before + ZoneMinder determines that the transfer has failed and closes + down the connection. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the maximum inactivity - timeout (in seconds) that should be tolerated before ZoneMinder - determines that the transfer has failed and closes down the - connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the maximum inactivity + timeout (in seconds) that should be tolerated before ZoneMinder + determines that the transfer has failed and closes down the + connection. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_STRICT", - default => "no", - description => "Require strict host key checking for SFTP uploads", - help => qqq(" - You can require SFTP uploads to verify the host key of the remote server - for protection against man-in-the-middle attacks. You will need to add the - server's key to the known_hosts file. On most systems, this will be - ~/.ssh/known_hosts, where ~ is the home directory of the web server running - ZoneMinder. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_STRICT', + default => 'no', + description => 'Require strict host key checking for SFTP uploads', + help => q` + You can require SFTP uploads to verify the host key of the remote server + for protection against man-in-the-middle attacks. You will need to add the + server's key to the known_hosts file. On most systems, this will be + ~/.ssh/known_hosts, where ~ is the home directory of the web server running + ZoneMinder. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_PASSIVE", - default => "yes", - description => "Use passive ftp when uploading", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates that ftp transfers - should be done in passive mode. This uses a single connection - for all ftp activity and, whilst slower than active transfers, - is more robust and likely to work from behind filewalls. This - option is ignored for SFTP transfers. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - If your computer is behind a firewall or proxy you may need to - set FTP to passive mode. In fact for simple transfers it makes - little sense to do otherwise anyway but you can set this to - 'No' if you wish. - "), + name => 'ZM_UPLOAD_FTP_PASSIVE', + default => 'yes', + description => 'Use passive ftp when uploading', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates that ftp transfers + should be done in passive mode. This uses a single connection + for all ftp activity and, whilst slower than active transfers, + is more robust and likely to work from behind filewalls. This + option is ignored for SFTP transfers. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + If your computer is behind a firewall or proxy you may need to + set FTP to passive mode. In fact for simple transfers it makes + little sense to do otherwise anyway but you can set this to + 'No' if you wish. + `, type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_DEBUG", - default => "no", - description => "Switch ftp debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. If you are having (or expecting) troubles - with uploading events then setting this to 'yes' permits - additional information to be included in the zmfilter log file. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_FTP_DEBUG', + default => 'no', + description => 'Switch ftp debugging on', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. If you are having (or expecting) troubles + with uploading events then setting this to 'yes' permits + additional information to be included in the zmfilter log file. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_DEBUG", - default => "no", - description => "Switch upload debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are having (or expecting) troubles with - uploading events then setting this to 'yes' permits additional - information to be generated by the underlying transfer modules - and included in the logs. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + name => 'ZM_UPLOAD_DEBUG', + default => 'no', + description => 'Switch upload debugging on', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are having (or expecting) troubles with + uploading events then setting this to 'yes' permits additional + information to be generated by the underlying transfer modules + and included in the logs. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_OPT_EMAIL", - default => "no", - description => "Should ZoneMinder email you details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - emailed to you at a designated email address. This will allow - you to be notified of events as soon as they occur and also to - quickly view the events directly. This option specifies whether - this functionality should be available. The email created with - this option can be any size and is intended to be sent to a - regular email reader rather than a mobile device. - "), + name => 'ZM_OPT_EMAIL', + default => 'no', + description => 'Should ZoneMinder email you details of events that match corresponding filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + emailed to you at a designated email address. This will allow + you to be notified of events as soon as they occur and also to + quickly view the events directly. This option specifies whether + this functionality should be available. The email created with + this option can be any size and is intended to be sent to a + regular email reader rather than a mobile device. + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the email address that any events - that match the appropriate filters will be sent to. - "), + name => 'ZM_EMAIL_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the email address that any events + that match the appropriate filters will be sent to. + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_TEXT", + name => 'ZM_EMAIL_TEXT', default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" body = " Hello, @@ -2228,30 +2256,30 @@ our @options = ( This alarm was matched by the %FN% filter and can be viewed at %EPS% ZoneMinder"', - description => "The text of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), + description => 'The text of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + `, type => $types{text}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_EMAIL_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)", - description => "The subject of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the email that is - sent for any events that match the appropriate filters. - "), + name => 'ZM_EMAIL_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', + description => 'The subject of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the subject of the email that is + sent for any events that match the appropriate filters. + `, type => $types{string}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_BODY", - default => " + name => 'ZM_EMAIL_BODY', + default => ' Hello, An alarm has been detected on your installation of the ZoneMinder. @@ -2266,1642 +2294,1600 @@ our @options = ( This alarm was matched by the %FN% filter and can be viewed at %EPS% - ZoneMinder", - description => "The body of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), + ZoneMinder', + description => 'The body of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + `, type => $types{text}, - category => "mail", + category => 'mail', }, { - name => "ZM_OPT_MESSAGE", - default => "no", - description => "Should ZoneMinder message you with details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - sent to you at a designated short message email address. This - will allow you to be notified of events as soon as they occur. - This option specifies whether this functionality should be - available. The email created by this option will be brief and - is intended to be sent to an SMS gateway or a minimal mail - reader such as a mobile device or phone rather than a regular - email reader. - "), + name => 'ZM_OPT_MESSAGE', + default => 'no', + description => 'Should ZoneMinder message you with details of events that match corresponding filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + sent to you at a designated short message email address. This + will allow you to be notified of events as soon as they occur. + This option specifies whether this functionality should be + available. The email created by this option will be brief and + is intended to be sent to an SMS gateway or a minimal mail + reader such as a mobile device or phone rather than a regular + email reader. + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the short message email address - that any events that match the appropriate filters will be sent - to. - "), + name => 'ZM_MESSAGE_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the short message email address + that any events that match the appropriate filters will be sent + to. + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_TEXT", + name => 'ZM_MESSAGE_TEXT', default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', - description => "The text of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), + description => 'The text of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + `, type => $types{text}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_MESSAGE_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI%", - description => "The subject of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the message that - is sent for any events that match the appropriate filters. - "), + name => 'ZM_MESSAGE_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI%', + description => 'The subject of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the subject of the message that + is sent for any events that match the appropriate filters. + `, type => $types{string}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_BODY", - default => "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.", - description => "The body of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), + name => 'ZM_MESSAGE_BODY', + default => 'ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', + description => 'The body of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + `, type => $types{text}, - category => "mail", + category => 'mail', }, { - name => "ZM_NEW_MAIL_MODULES", - default => "no", - description => "Use a newer perl method to send emails", + name => 'ZM_NEW_MAIL_MODULES', + default => 'no', + description => 'Use a newer perl method to send emails', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" - Traditionally ZoneMinder has used the MIME::Entity perl module - to construct and send notification emails and messages. Some - people have reported problems with this module not being - present at all or flexible enough for their needs. If you are - one of those people this option allows you to select a new - mailing method using MIME::Lite and Net::SMTP instead. This - method was contributed by Ross Melin and should work for - everyone but has not been extensively tested so currently is - not selected by default. - "), + help => q` + Traditionally ZoneMinder has used the MIME::Entity perl module + to construct and send notification emails and messages. Some + people have reported problems with this module not being + present at all or flexible enough for their needs. If you are + one of those people this option allows you to select a new + mailing method using MIME::Lite and Net::SMTP instead. This + method was contributed by Ross Melin and should work for + everyone but has not been extensively tested so currently is + not selected by default. + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_HOST", - default => "localhost", - description => "The host address of your SMTP mail server", + name => 'ZM_EMAIL_HOST', + default => 'localhost', + description => 'The host address of your SMTP mail server', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" - If you have chosen SMTP as the method by which to send - notification emails or messages then this option allows you to - choose which SMTP server to use to send them. The default of - localhost may work if you have the sendmail, exim or a similar - daemon running however you may wish to enter your ISP's SMTP - mail server here. - "), + help => q` + If you have chosen SMTP as the method by which to send + notification emails or messages then this option allows you to + choose which SMTP server to use to send them. The default of + localhost may work if you have the sendmail, exim or a similar + daemon running however you may wish to enter your ISP's SMTP + mail server here. + `, type => $types{hostname}, - category => "mail", + category => 'mail', }, { - name => "ZM_FROM_EMAIL", - default => "", - description => "The email address you wish your event notifications to originate from", + name => 'ZM_FROM_EMAIL', + default => '', + description => 'The email address you wish your event notifications to originate from', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" - The emails or messages that will be sent to you informing you - of events can appear to come from a designated email address to - help you with mail filtering etc. An address of something like - ZoneMinder\@your.domain is recommended. - "), + help => q` + The emails or messages that will be sent to you informing you + of events can appear to come from a designated email address to + help you with mail filtering etc. An address of something like + ZoneMinder\@your.domain is recommended. + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_URL", - default => "", - description => "The URL of your ZoneMinder installation", + name => 'ZM_URL', + default => '', + description => 'The URL of your ZoneMinder installation', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" - 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.php. - "), + help => q` + 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.php. + `, type => $types{url}, - category => "mail", + category => 'mail', }, { - name => "ZM_MAX_RESTART_DELAY", - default => "600", - description => "Maximum delay (in seconds) for daemon restart attempts.", - help => qqq(" - The zmdc (zm daemon control) process controls when processeses - are started or stopped and will attempt to restart any that - fail. If a daemon fails frequently then a delay is introduced - between each restart attempt. If the daemon stills fails then - this delay is increased to prevent extra load being placed on - the system by continual restarts. This option controls what - this maximum delay is. - "), + name => 'ZM_MAX_RESTART_DELAY', + default => '600', + description => 'Maximum delay (in seconds) for daemon restart attempts.', + help => q` + The zmdc (zm daemon control) process controls when processeses + are started or stopped and will attempt to restart any that + fail. If a daemon fails frequently then a delay is introduced + between each restart attempt. If the daemon stills fails then + this delay is increased to prevent extra load being placed on + the system by continual restarts. This option controls what + this maximum delay is. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_WATCH_CHECK_INTERVAL", - default => "10", - description => "How often to check the capture daemons have not locked up", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines how often the daemons are checked. - "), + name => 'ZM_WATCH_CHECK_INTERVAL', + default => '10', + description => 'How often to check the capture daemons have not locked up', + help => q` + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines how often the daemons are checked. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_WATCH_MAX_DELAY", - default => "5", - description => "The maximum delay allowed since the last captured image", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines the maximum delay to allow since the last captured - frame. The daemon will be restarted if it has not captured any - images after this period though the actual restart may take - slightly longer in conjunction with the check interval value - above. - "), + name => 'ZM_WATCH_MAX_DELAY', + default => '5', + description => 'The maximum delay allowed since the last captured image', + help => q` + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines the maximum delay to allow since the last captured + frame. The daemon will be restarted if it has not captured any + images after this period though the actual restart may take + slightly longer in conjunction with the check interval value + above. + `, type => $types{decimal}, - category => "system", + category => 'system', }, { - - name => "ZM_RUN_AUDIT", - default => "yes", - description => "Run zmaudit to check data consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. This - option controls whether zmaudit is run in the background and - performs these checks and fixes continuously. This is - recommended for most systems however if you have a very large - number of events the process of scanning the database and - filesystem may take a long time and impact performance. In this - case you may prefer to not have zmaudit running unconditionally - and schedule occasional checks at other, more convenient, - times. - "), + name => 'ZM_RUN_AUDIT', + default => 'yes', + description => 'Run zmaudit to check data consistency', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. This + option controls whether zmaudit is run in the background and + performs these checks and fixes continuously. This is + recommended for most systems however if you have a very large + number of events the process of scanning the database and + filesystem may take a long time and impact performance. In this + case you may prefer to not have zmaudit running unconditionally + and schedule occasional checks at other, more convenient, + times. + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUDIT_CHECK_INTERVAL", - default => "900", - description => "How often to check database and filesystem consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. The - default check interval of 900 seconds (15 minutes) is fine for - most systems however if you have a very large number of events - the process of scanning the database and filesystem may take a - long time and impact performance. In this case you may prefer - to make this interval much larger to reduce the impact on your - system. This option determines how often these checks are - performed. - "), + name => 'ZM_AUDIT_CHECK_INTERVAL', + default => '900', + description => 'How often to check database and filesystem consistency', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. The + default check interval of 900 seconds (15 minutes) is fine for + most systems however if you have a very large number of events + the process of scanning the database and filesystem may take a + long time and impact performance. In this case you may prefer + to make this interval much larger to reduce the impact on your + system. This option determines how often these checks are + performed. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_AUDIT_MIN_AGE", - default => "86400", - description => "The minimum age in seconds event data must be in order to be deleted.", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. Event files or db records that are younger than - this setting will not be deleted and a warning will be given. - "), + name => 'ZM_AUDIT_MIN_AGE', + default => '86400', + description => 'The minimum age in seconds event data must be in order to be deleted.', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. Event files or db records that are younger than + this setting will not be deleted and a warning will be given. + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_FORCED_ALARM_SCORE", - default => "255", - description => "Score to give forced alarms", - help => qqq(" - The 'zmu' utility can be used to force an alarm on a monitor - rather than rely on the motion detection algorithms. This - option determines what score to give these alarms to - distinguish them from regular ones. It must be 255 or less. - "), + name => 'ZM_FORCED_ALARM_SCORE', + default => '255', + description => 'Score to give forced alarms', + help => q` + The 'zmu' utility can be used to force an alarm on a monitor + rather than rely on the motion detection algorithms. This + option determines what score to give these alarms to + distinguish them from regular ones. It must be 255 or less. + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_BULK_FRAME_INTERVAL", - default => "100", - description => "How often a bulk frame should be written to the database", - help => qqq(" - Traditionally ZoneMinder writes an entry into the Frames - database table for each frame that is captured and saved. This - works well in motion detection scenarios but when in a DVR - situation ('Record' or 'Mocord' mode) this results in a huge - number of frame writes and a lot of database and disk bandwidth - for very little additional information. Setting this to a - non-zero value will enabled ZoneMinder to group these non-alarm - frames into one 'bulk' frame entry which saves a lot of - bandwidth and space. The only disadvantage of this is that - timing information for individual frames is lost but in - constant frame rate situations this is usually not significant. - This setting is ignored in Modect mode and individual frames - are still written if an alarm occurs in Mocord mode also. - "), + name => 'ZM_BULK_FRAME_INTERVAL', + default => '100', + description => 'How often a bulk frame should be written to the database', + help => q` + Traditionally ZoneMinder writes an entry into the Frames + database table for each frame that is captured and saved. This + works well in motion detection scenarios but when in a DVR + situation ('Record' or 'Mocord' mode) this results in a huge + number of frame writes and a lot of database and disk bandwidth + for very little additional information. Setting this to a + non-zero value will enabled ZoneMinder to group these non-alarm + frames into one 'bulk' frame entry which saves a lot of + bandwidth and space. The only disadvantage of this is that + timing information for individual frames is lost but in + constant frame rate situations this is usually not significant. + This setting is ignored in Modect mode and individual frames + are still written if an alarm occurs in Mocord mode also. + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_EVENT_CLOSE_MODE", - default => "idle", - description => "When continuous events are closed.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section. This option controls what happens when an alarm occurs - in Mocord mode. The 'time' setting means that the event will be - closed at the end of the section regardless of alarm activity. - The 'idle' setting means that the event will be closed at the - end of the section if there is no alarm activity occurring at - the time otherwise it will be closed once the alarm is over - meaning the event may end up being longer than the normal - section length. The 'alarm' setting means that if an alarm - occurs during the event, the event will be closed once the - alarm is over regardless of when this occurs. This has the - effect of limiting the number of alarms to one per event and - the events will be shorter than the section length if an alarm - has occurred. - "), + name => 'ZM_EVENT_CLOSE_MODE', + default => 'idle', + description => 'When continuous events are closed.', + help => q` + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section. This option controls what happens when an alarm occurs + in Mocord mode. The 'time' setting means that the event will be + closed at the end of the section regardless of alarm activity. + The 'idle' setting means that the event will be closed at the + end of the section if there is no alarm activity occurring at + the time otherwise it will be closed once the alarm is over + meaning the event may end up being longer than the normal + section length. The 'alarm' setting means that if an alarm + occurs during the event, the event will be closed once the + alarm is over regardless of when this occurs. This has the + effect of limiting the number of alarms to one per event and + the events will be shorter than the section length if an alarm + has occurred. + `, type => $types{boolean}, type => { - db_type =>"string", - hint =>"time|idle|alarm", + db_type =>'string', + hint =>'time|idle|alarm', pattern =>qr|^([tia])|i, format =>q( ($1 =~ /^t/) - ? "time" - : ($1 =~ /^i/ ? "idle" : "time" ) + ? 'time' + : ($1 =~ /^i/ ? 'idle' : 'time' ) ) }, - category => "config", + category => 'config', }, # Deprecated, superseded by event close mode { - name => "ZM_FORCE_CLOSE_EVENTS", - default => "no", - description => "Close events at section ends.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section and ordinarily this will prevent the event being closed - until the motion has ceased. Switching this option on will - force the event closed at the specified time regardless of any - motion activity. - "), + name => 'ZM_FORCE_CLOSE_EVENTS', + default => 'no', + description => 'Close events at section ends.', + help => q` + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section and ordinarily this will prevent the event being closed + until the motion has ceased. Switching this option on will + force the event closed at the specified time regardless of any + motion activity. + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_CREATE_ANALYSIS_IMAGES", - default => "yes", - description => "Create analysed alarm images with motion outlined", - help => qqq(" - By default during an alarm ZoneMinder records both the raw - captured image and one that has been analysed and had areas - where motion was detected outlined. This can be very useful - during zone configuration or in analysing why events occurred. - However it also incurs some overhead and in a stable system may - no longer be necessary. This parameter allows you to switch the - generation of these images off. - "), + name => 'ZM_CREATE_ANALYSIS_IMAGES', + default => 'yes', + description => 'Create analysed alarm images with motion outlined', + help => q` + By default during an alarm ZoneMinder records both the raw + captured image and one that has been analysed and had areas + where motion was detected outlined. This can be very useful + during zone configuration or in analysing why events occurred. + However it also incurs some overhead and in a stable system may + no longer be necessary. This parameter allows you to switch the + generation of these images off. + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_WEIGHTED_ALARM_CENTRES", - default => "no", - description => "Use a weighted algorithm to calculate the centre of an alarm", - help => qqq(" - ZoneMinder will always calculate the centre point of an alarm - in a zone to give some indication of where on the screen it is. - This can be used by the experimental motion tracking feature or - your own custom extensions. In the alarmed or filtered pixels - mode this is a simple midpoint between the extents of the - detected pixels. However in the blob method this can instead be - calculated using weighted pixel locations to give more accurate - positioning for irregularly shaped blobs. This method, while - more precise is also slower and so is turned off by default. - "), + name => 'ZM_WEIGHTED_ALARM_CENTRES', + default => 'no', + description => 'Use a weighted algorithm to calculate the centre of an alarm', + help => q` + ZoneMinder will always calculate the centre point of an alarm + in a zone to give some indication of where on the screen it is. + This can be used by the experimental motion tracking feature or + your own custom extensions. In the alarmed or filtered pixels + mode this is a simple midpoint between the extents of the + detected pixels. However in the blob method this can instead be + calculated using weighted pixel locations to give more accurate + positioning for irregularly shaped blobs. This method, while + more precise is also slower and so is turned off by default. + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_EVENT_IMAGE_DIGITS", - default => "5", - description => "How many significant digits are used in event image numbering", - help => qqq(" - As event images are captured they are stored to the filesystem - with a numerical index. By default this index has three digits - so the numbers start 001, 002 etc. This works works for most - scenarios as events with more than 999 frames are rarely - captured. However if you have extremely long events and use - external applications then you may wish to increase this to - ensure correct sorting of images in listings etc. Warning, - increasing this value on a live system may render existing - events unviewable as the event will have been saved with the - previous scheme. Decreasing this value should have no ill - effects. - "), + name => 'ZM_EVENT_IMAGE_DIGITS', + default => '5', + description => 'How many significant digits are used in event image numbering', + help => q` + As event images are captured they are stored to the filesystem + with a numerical index. By default this index has three digits + so the numbers start 001, 002 etc. This works works for most + scenarios as events with more than 999 frames are rarely + captured. However if you have extremely long events and use + external applications then you may wish to increase this to + ensure correct sorting of images in listings etc. Warning, + increasing this value on a live system may render existing + events unviewable as the event will have been saved with the + previous scheme. Decreasing this value should have no ill + effects. + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_DEFAULT_ASPECT_RATIO", - default => "4:3", - description => "The default width:height aspect ratio used in monitors", - help => qqq(" - When specifying the dimensions of monitors you can click a - checkbox to ensure that the width stays in the correct ratio to - the height, or vice versa. This setting allows you to indicate - what the ratio of these settings should be. This should be - specified in the format : and the - default of 4:3 normally be acceptable but 11:9 is another - common setting. If the checkbox is not clicked when specifying - monitor dimensions this setting has no effect. - "), + name => 'ZM_DEFAULT_ASPECT_RATIO', + default => '4:3', + description => 'The default width:height aspect ratio used in monitors', + help => q` + When specifying the dimensions of monitors you can click a + checkbox to ensure that the width stays in the correct ratio to + the height, or vice versa. This setting allows you to indicate + what the ratio of these settings should be. This should be + specified in the format : and the + default of 4:3 normally be acceptable but 11:9 is another + common setting. If the checkbox is not clicked when specifying + monitor dimensions this setting has no effect. + `, type => $types{string}, - category => "config", + category => 'config', }, { - name => "ZM_USER_SELF_EDIT", - default => "no", - description => "Allow unprivileged users to change their details", - help => qqq(" - Ordinarily only users with system edit privilege are able to - change users details. Switching this option on allows ordinary - users to change their passwords and their language settings - "), + name => 'ZM_USER_SELF_EDIT', + default => 'no', + description => 'Allow unprivileged users to change their details', + help => q` + Ordinarily only users with system edit privilege are able to + change users details. Switching this option on allows ordinary + users to change their passwords and their language settings + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_OPT_FRAME_SERVER", - default => "no", - description => "Should analysis farm out the writing of images to disk", - #requires => [ { name => "ZM_OPT_ADAPTIVE_SKIP", value => "yes" } ], - help => qqq(" - In some circumstances it is possible for a slow disk to take so - long writing images to disk that it causes the analysis daemon - to fall behind especially during high frame rate events. - Setting this option to yes enables a frame server daemon (zmf) - which will be sent the images from the analysis daemon and will - do the actual writing of images itself freeing up the analysis - daemon to get on with other things. Should this transmission - fail or other permanent or transient error occur, this function - will fall back to the analysis daemon. - "), + name => 'ZM_OPT_CONTROL', + default => 'no', + description => 'Support controllable (e.g. PTZ) cameras', + help => q` + ZoneMinder includes limited support for controllable cameras. A + number of sample protocols are included and others can easily + be added. If you wish to control your cameras via ZoneMinder + then select this option otherwise if you only have static + cameras or use other control methods then leave this option + off. + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_FRAME_SOCKET_SIZE", - default => "0", - description => "Specify the frame server socket buffer size if non-standard", - requires => [ { name => "ZM_OPT_FRAME_SERVER", value => "yes" } ], - help => qqq(" - For large captured images it is possible for the writes from - the analysis daemon to the frame server to fail as the amount - to be written exceeds the default buffer size. While the images - are then written by the analysis daemon so no data is lost, it - defeats the object of the frame server daemon in the first - place. You can use this option to indicate that a larger buffer - size should be used. Note that you may have to change the - existing maximum socket buffer size on your system via sysctl - (or in /proc/sys/net/core/wmem_max) to allow this new size to - be set. Alternatively you can change the default buffer size on - your system in the same way in which case that will be used - with no change necessary in this option - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_OPT_CONTROL", - default => "no", - description => "Support controllable (e.g. PTZ) cameras", - help => qqq(" - ZoneMinder includes limited support for controllable cameras. A - number of sample protocols are included and others can easily - be added. If you wish to control your cameras via ZoneMinder - then select this option otherwise if you only have static - cameras or use other control methods then leave this option - off. - "), + name => 'ZM_OPT_TRIGGERS', + default => 'no', + description => 'Interface external event triggers via socket or device files', + help => q` + ZoneMinder can interact with external systems which prompt or + cancel alarms. This is done via the zmtrigger.pl script. This + option indicates whether you want to use these external + triggers. Most people will say no here. + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_TRIGGERS", - default => "no", - description => "Interface external event triggers via socket or device files", - help => qqq(" - ZoneMinder can interact with external systems which prompt or - cancel alarms. This is done via the zmtrigger.pl script. This - option indicates whether you want to use these external - triggers. Most people will say no here. - "), + name => 'ZM_CHECK_FOR_UPDATES', + default => 'yes', + description => 'Check with zoneminder.com for updated versions', + help => q` + From ZoneMinder version 1.17.0 onwards new versions are + expected to be more frequent. To save checking manually for + each new version ZoneMinder can check with the zoneminder.com + website to determine the most recent release. These checks are + infrequent, about once per week, and no personal or system + information is transmitted other than your current version + number. If you do not wish these checks to take place or your + ZoneMinder system has no internet access you can switch these + check off with this configuration variable + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_CHECK_FOR_UPDATES", - default => "yes", - description => "Check with zoneminder.com for updated versions", - help => qqq(" - From ZoneMinder version 1.17.0 onwards new versions are - expected to be more frequent. To save checking manually for - each new version ZoneMinder can check with the zoneminder.com - website to determine the most recent release. These checks are - infrequent, about once per week, and no personal or system - information is transmitted other than your current version - number. If you do not wish these checks to take place or your - ZoneMinder system has no internet access you can switch these - check off with this configuration variable - "), + name => 'ZM_TELEMETRY_DATA', + default => 'yes', + description => 'Send usage information to ZoneMinder', + help => q` + Enable collection of usage information of the local system and send + it to the ZoneMinder development team. This data will be used to + determine things like who and where our customers are, how big their + systems are, the underlying hardware and operating system, etc. + This is being done for the sole purpoase of creating a better + product for our target audience. This script is intended to be + completely transparent to the end user, and can be disabled from + the web console under Options. + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_TELEMETRY_DATA", - default => "yes", - description => "Send usage information to ZoneMinder", - help => qqq(" - Enable collection of usage information of the local system and send - it to the ZoneMinder development team. This data will be used to - determine things like who and where our customers are, how big their - systems are, the underlying hardware and operating system, etc. - This is being done for the sole purpoase of creating a better - product for our target audience. This script is intended to be - completely transparent to the end user, and can be disabled from - the web console under Options. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_TELEMETRY_UUID", - default => "", - description => "Unique identifier for ZoneMinder telemetry", - help => qqq(" - This variable is auto-generated once by the system and is used to - uniquely identify it among all other ZoneMinder systems in - existence. - "), + name => 'ZM_TELEMETRY_UUID', + default => '', + description => 'Unique identifier for ZoneMinder telemetry', + help => q` + This variable is auto-generated once by the system and is used to + uniquely identify it among all other ZoneMinder systems in + existence. + `, type => $types{string}, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_TELEMETRY_LAST_UPLOAD", - default => "", - description => "When the last ZoneMinder telemetry upload ocurred", - help => "", + name => 'ZM_TELEMETRY_LAST_UPLOAD', + default => '', + description => 'When the last ZoneMinder telemetry upload ocurred', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_UPDATE_CHECK_PROXY", - default => "", - description => "Proxy url if required to access zoneminder.com", - help => qqq(" - If you use a proxy to access the internet then ZoneMinder needs - to know so it can access zoneminder.com to check for updates. - If you do use a proxy enter the full proxy url here in the form - of http://:/ - "), + name => 'ZM_UPDATE_CHECK_PROXY', + default => '', + description => 'Proxy url if required to access zoneminder.com', + help => q` + If you use a proxy to access the internet then ZoneMinder needs + to know so it can access zoneminder.com to check for updates. + If you do use a proxy enter the full proxy url here in the form + of http://:/ + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_SHM_KEY", - default => "0x7a6d0000", - description => "Shared memory root key to use", - help => qqq(" - ZoneMinder uses shared memory to speed up communication between - modules. To identify the right area to use shared memory keys - are used. This option controls what the base key is, each - monitor will have it's Id or'ed with this to get the actual key - used. You will not normally need to change this value unless it - clashes with another instance of ZoneMinder on the same - machine. Only the first four hex digits are used, the lower - four will be masked out and ignored. - "), + name => 'ZM_SHM_KEY', + default => '0x7a6d0000', + description => 'Shared memory root key to use', + help => q` + ZoneMinder uses shared memory to speed up communication between + modules. To identify the right area to use shared memory keys + are used. This option controls what the base key is, each + monitor will have it's Id or'ed with this to get the actual key + used. You will not normally need to change this value unless it + clashes with another instance of ZoneMinder on the same + machine. Only the first four hex digits are used, the lower + four will be masked out and ignored. + `, type => $types{hexadecimal}, - category => "system", + category => 'system', }, # Deprecated, really no longer necessary { - name => "ZM_WEB_REFRESH_METHOD", - default => "javascript", - description => "What method windows should use to refresh themselves", - help => qqq(" - Many windows in Javascript need to refresh themselves to keep - their information current. This option determines what method - they should use to do this. Choosing 'javascript' means that - each window will have a short JavaScript statement in with a - timer to prompt the refresh. This is the most compatible - method. Choosing 'http' means the refresh instruction is put in - the HTTP header. This is a cleaner method but refreshes are - interrupted or cancelled when a link in the window is clicked - meaning that the window will no longer refresh and this would - have to be done manually. - "), + name => 'ZM_WEB_REFRESH_METHOD', + default => 'javascript', + description => 'What method windows should use to refresh themselves', + help => q` + Many windows in Javascript need to refresh themselves to keep + their information current. This option determines what method + they should use to do this. Choosing 'javascript' means that + each window will have a short JavaScript statement in with a + timer to prompt the refresh. This is the most compatible + method. Choosing 'http' means the refresh instruction is put in + the HTTP header. This is a cleaner method but refreshes are + interrupted or cancelled when a link in the window is clicked + meaning that the window will no longer refresh and this would + have to be done manually. + `, type => { - db_type =>"string", - hint =>"javascript|http", + db_type =>'string', + hint =>'javascript|http', pattern =>qr|^([jh])|i, format =>q( $1 =~ /^j/ - ? "javascript" - : "http" + ? 'javascript' + : 'http' ) }, - category => "hidden", + category => 'hidden', }, { - name => "ZM_WEB_EVENT_SORT_FIELD", - default => "DateTime", - description => "Default field the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what field is used to sort them. You can - modify this ordering from filters or by clicking on headings in - the lists themselves. Bear in mind however that the 'Prev' and - 'Next' links, when scrolling through events, relate to the - ordering in the lists and so not always to time based ordering. - "), + name => 'ZM_WEB_EVENT_SORT_FIELD', + default => 'DateTime', + description => 'Default field the event lists are sorted by', + help => q` + Events in lists can be initially ordered in any way you want. + This option controls what field is used to sort them. You can + modify this ordering from filters or by clicking on headings in + the lists themselves. Bear in mind however that the 'Prev' and + 'Next' links, when scrolling through events, relate to the + ordering in the lists and so not always to time based ordering. + `, type => { - db_type =>"string", - hint =>"Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore", + db_type =>'string', + hint =>'Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, - category => "web", + category => 'web', }, { - name => "ZM_WEB_EVENT_SORT_ORDER", - default => "asc", - description => "Default order the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what order (ascending or descending) is - used to sort them. You can modify this ordering from filters or - by clicking on headings in the lists themselves. Bear in mind - however that the 'Prev' and 'Next' links, when scrolling - through events, relate to the ordering in the lists and so not - always to time based ordering. - "), + name => 'ZM_WEB_EVENT_SORT_ORDER', + default => 'asc', + description => 'Default order the event lists are sorted by', + help => q` + Events in lists can be initially ordered in any way you want. + This option controls what order (ascending or descending) is + used to sort them. You can modify this ordering from filters or + by clicking on headings in the lists themselves. Bear in mind + however that the 'Prev' and 'Next' links, when scrolling + through events, relate to the ordering in the lists and so not + always to time based ordering. + `, type => { - db_type =>"string", - hint =>"asc|desc", + db_type =>'string', + hint =>'asc|desc', pattern =>qr|^([ad])|i, - format =>q( $1 =~ /^a/i ? "asc" : "desc" ) + format =>q( $1 =~ /^a/i ? 'asc' : 'desc' ) }, - category => "web", + category => 'web', }, { - name => "ZM_WEB_EVENTS_PER_PAGE", - default => "25", - description => "How many events to list per page in paged mode", - help => qqq(" - In the event list view you can either list all events or just a - page at a time. This option controls how many events are listed - per page in paged mode and how often to repeat the column - headers in non-paged mode. - "), + name => 'ZM_WEB_EVENTS_PER_PAGE', + default => '25', + description => 'How many events to list per page in paged mode', + help => q` + In the event list view you can either list all events or just a + page at a time. This option controls how many events are listed + per page in paged mode and how often to repeat the column + headers in non-paged mode. + `, type => $types{integer}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_LIST_THUMBS", - default => "no", - description => "Display mini-thumbnails of event images in event lists", - help => qqq(" - Ordinarily the event lists just display text details of the - events to save space and time. By switching this option on you - can also display small thumbnails to help you identify events - of interest. The size of these thumbnails is controlled by the - following two options. - "), + name => 'ZM_WEB_LIST_THUMBS', + default => 'no', + description => 'Display mini-thumbnails of event images in event lists', + help => q` + Ordinarily the event lists just display text details of the + events to save space and time. By switching this option on you + can also display small thumbnails to help you identify events + of interest. The size of these thumbnails is controlled by the + following two options. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_LIST_THUMB_WIDTH", - default => "48", - description => "The width of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the width of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - height instead in the next option but you should only use one - of the width or height and the other option should be set to - zero. If both width and height are specified then width will be - used and height ignored. - "), + name => 'ZM_WEB_LIST_THUMB_WIDTH', + default => '48', + description => 'The width of the thumbnails that appear in the event lists', + help => q` + This options controls the width of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + height instead in the next option but you should only use one + of the width or height and the other option should be set to + zero. If both width and height are specified then width will be + used and height ignored. + `, type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_LIST_THUMB_HEIGHT", - default => "0", - description => "The height of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the height of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - width instead in the previous option but you should only use - one of the width or height and the other option should be set - to zero. If both width and height are specified then width will - be used and height ignored. - "), + name => 'ZM_WEB_LIST_THUMB_HEIGHT', + default => '0', + description => 'The height of the thumbnails that appear in the event lists', + help => q` + This options controls the height of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + width instead in the previous option but you should only use + one of the width or height and the other option should be set + to zero. If both width and height are specified then width will + be used and height ignored. + `, type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_USE_OBJECT_TAGS", - default => "yes", - description => "Wrap embed in object tags for media content", - help => qqq(" - There are two methods of including media content in web pages. - The most common way is use the EMBED tag which is able to give - some indication of the type of content. However this is not a - standard part of HTML. The official method is to use OBJECT - tags which are able to give more information allowing the - correct media viewers etc to be loaded. However these are less - widely supported and content may be specifically tailored to a - particular platform or player. This option controls whether - media content is enclosed in EMBED tags only or whether, where - appropriate, it is additionally wrapped in OBJECT tags. - Currently OBJECT tags are only used in a limited number of - circumstances but they may become more widespread in the - future. It is suggested that you leave this option on unless - you encounter problems playing some content. - "), + name => 'ZM_WEB_USE_OBJECT_TAGS', + default => 'yes', + description => 'Wrap embed in object tags for media content', + help => q` + There are two methods of including media content in web pages. + The most common way is use the EMBED tag which is able to give + some indication of the type of content. However this is not a + standard part of HTML. The official method is to use OBJECT + tags which are able to give more information allowing the + correct media viewers etc to be loaded. However these are less + widely supported and content may be specifically tailored to a + particular platform or player. This option controls whether + media content is enclosed in EMBED tags only or whether, where + appropriate, it is additionally wrapped in OBJECT tags. + Currently OBJECT tags are only used in a limited number of + circumstances but they may become more widespread in the + future. It is suggested that you leave this option on unless + you encounter problems playing some content. + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_H_REFRESH_MAIN", - default => "60", - introduction => qqq(" - There are now a number of options that are grouped into - bandwidth categories, this allows you to configure the - ZoneMinder client to work optimally over the various access - methods you might to access the client.\n\nThe next few options - control what happens when the client is running in 'high' - bandwidth mode. You should set these options for when accessing - the ZoneMinder client over a local network or high speed link. - In most cases the default values will be suitable as a starting - point. - "), - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), + name => 'ZM_WEB_H_REFRESH_MAIN', + default => '60', + introduction => q` + There are now a number of options that are grouped into + bandwidth categories, this allows you to configure the + ZoneMinder client to work optimally over the various access + methods you might to access the client.\n\nThe next few options + control what happens when the client is running in 'high' + bandwidth mode. You should set these options for when accessing + the ZoneMinder client over a local network or high speed link. + In most cases the default values will be suitable as a starting + point. + `, + description => 'How often (in seconds) the main console window should refresh itself', + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_CYCLE", - default => "10", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), + name => 'ZM_WEB_H_REFRESH_CYCLE', + default => '10', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_IMAGE", - default => "3", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), + name => 'ZM_WEB_H_REFRESH_IMAGE', + default => '3', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_STATUS", - default => "1", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), + name => 'ZM_WEB_H_REFRESH_STATUS', + default => '1', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_EVENTS", - default => "5", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), + name => 'ZM_WEB_H_REFRESH_EVENTS', + default => '5', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + name => 'ZM_WEB_H_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, type => $types{tristate}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), + name => 'ZM_WEB_H_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", + db_type =>'string', + hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), + name => 'ZM_WEB_H_DEFAULT_SCALE', + default => '100', + description => 'What the default scaling factor applied to \'live\' or \'event\' views is (%)', + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), + name => 'ZM_WEB_H_DEFAULT_RATE', + default => '100', + description => 'What the default replay rate factor applied to \'event\' views is (%)', + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_VIDEO_BITRATE", - default => "150000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), + name => 'ZM_WEB_H_VIDEO_BITRATE', + default => '150000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_VIDEO_MAXFPS", - default => "30", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), + name => 'ZM_WEB_H_VIDEO_MAXFPS', + default => '30', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_SCALE_THUMBS", - default => "no", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), + name => 'ZM_WEB_H_SCALE_THUMBS', + default => 'no', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, type => $types{boolean}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), + name => 'ZM_WEB_H_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, type => { - db_type =>"string", - hint =>"events|timeline", + db_type =>'string', + hint =>'events|timeline', pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), + name => 'ZM_WEB_H_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, type => $types{boolean}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_AJAX_TIMEOUT", - default => "3000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), + name => 'ZM_WEB_H_AJAX_TIMEOUT', + default => '3000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_M_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), + name => 'ZM_WEB_M_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, type => $types{integer}, - introduction => qqq(" - The next few options control what happens when the client is - running in 'medium' bandwidth mode. You should set these - options for when accessing the ZoneMinder client over a slower - cable or DSL link. In most cases the default values will be - suitable as a starting point. - "), - category => "medband", + introduction => q` + The next few options control what happens when the client is + running in 'medium' bandwidth mode. You should set these + options for when accessing the ZoneMinder client over a slower + cable or DSL link. In most cases the default values will be + suitable as a starting point. + `, + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_CYCLE", - default => "20", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), + name => 'ZM_WEB_M_REFRESH_CYCLE', + default => '20', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_IMAGE", - default => "10", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), + name => 'ZM_WEB_M_REFRESH_IMAGE', + default => '10', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_STATUS", - default => "5", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), + name => 'ZM_WEB_M_REFRESH_STATUS', + default => '5', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_EVENTS", - default => "60", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), + name => 'ZM_WEB_M_REFRESH_EVENTS', + default => '60', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + name => 'ZM_WEB_M_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, type => $types{tristate}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), + name => 'ZM_WEB_M_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", + db_type =>'string', + hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), + name => 'ZM_WEB_M_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), + name => 'ZM_WEB_M_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_VIDEO_BITRATE", - default => "75000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), + name => 'ZM_WEB_M_VIDEO_BITRATE', + default => '75000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_VIDEO_MAXFPS", - default => "10", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), + name => 'ZM_WEB_M_VIDEO_MAXFPS', + default => '10', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), + name => 'ZM_WEB_M_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, type => $types{boolean}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), + name => 'ZM_WEB_M_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, type => { - db_type =>"string", - hint =>"events|timeline", + db_type =>'string', + hint =>'events|timeline', pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), + name => 'ZM_WEB_M_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, type => $types{boolean}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_AJAX_TIMEOUT", - default => "5000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), + name => 'ZM_WEB_M_AJAX_TIMEOUT', + default => '5000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_L_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - introduction => qqq(" - The next few options control what happens when the client is - running in 'low' bandwidth mode. You should set these options - for when accessing the ZoneMinder client over a modem or slow - link. In most cases the default values will be suitable as a - starting point. - "), - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), + name => 'ZM_WEB_L_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + introduction => q` + The next few options control what happens when the client is + running in 'low' bandwidth mode. You should set these options + for when accessing the ZoneMinder client over a modem or slow + link. In most cases the default values will be suitable as a + starting point. + `, + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_CYCLE", - default => "30", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), + name => 'ZM_WEB_L_REFRESH_CYCLE', + default => '30', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_IMAGE", - default => "15", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), + name => 'ZM_WEB_L_REFRESH_IMAGE', + default => '15', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_STATUS", - default => "10", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), + name => 'ZM_WEB_L_REFRESH_STATUS', + default => '10', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_EVENTS", - default => "180", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), + name => 'ZM_WEB_L_REFRESH_EVENTS', + default => '180', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + name => 'ZM_WEB_L_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, type => $types{tristate}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), + name => 'ZM_WEB_L_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", + db_type =>'string', + hint =>'mpeg|jpeg', pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), + name => 'ZM_WEB_L_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', pattern =>qr|^(\d+)$|, format =>q( $1 ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), + name => 'ZM_WEB_L_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', pattern =>qr|^(\d+)$|, format=>q( $1 ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_VIDEO_BITRATE", - default => "25000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), + name => 'ZM_WEB_L_VIDEO_BITRATE', + default => '25000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_VIDEO_MAXFPS", - default => "5", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), + name => 'ZM_WEB_L_VIDEO_MAXFPS', + default => '5', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), + name => 'ZM_WEB_L_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, type => $types{boolean}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), + name => 'ZM_WEB_L_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, type => { - db_type =>"string", - hint =>"events|timeline", + db_type =>'string', + hint =>'events|timeline', pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_SHOW_PROGRESS", - default => "no", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), + name => 'ZM_WEB_L_SHOW_PROGRESS', + default => 'no', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, type => $types{boolean}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_AJAX_TIMEOUT", - default => "10000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), + name => 'ZM_WEB_L_AJAX_TIMEOUT', + default => '10000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_DYN_LAST_VERSION", - default => "", - description => "What the last version of ZoneMinder recorded from zoneminder.com is", - help => "", + name => 'ZM_DYN_LAST_VERSION', + default => '', + description => 'What the last version of ZoneMinder recorded from zoneminder.com is', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_CURR_VERSION", - default => "@VERSION@", - description => qqq(" - What the effective current version of ZoneMinder is, might be - different from actual if versions ignored - "), - help => "", + name => 'ZM_DYN_CURR_VERSION', + default => '@VERSION@', + description => q` + What the effective current version of ZoneMinder is, might be + different from actual if versions ignored + `, + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_DB_VERSION", - default => "@VERSION@", - description => "What the version of the database is, from zmupdate", - help => "", + name => 'ZM_DYN_DB_VERSION', + default => '@VERSION@', + description => 'What the version of the database is, from zmupdate', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_LAST_CHECK", - default => "", - description => "When the last check for version from zoneminder.com was", - help => "", + name => 'ZM_DYN_LAST_CHECK', + default => '', + description => 'When the last check for version from zoneminder.com was', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_NEXT_REMINDER", - default => "", - description => "When the earliest time to remind about versions will be", - help => "", + name => 'ZM_DYN_NEXT_REMINDER', + default => '', + description => 'When the earliest time to remind about versions will be', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_DONATE_REMINDER_TIME", + name => 'ZM_DYN_DONATE_REMINDER_TIME', default => 0, - description => "When the earliest time to remind about donations will be", - help => "", + description => 'When the earliest time to remind about donations will be', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_SHOW_DONATE_REMINDER", - default => "yes", - description => "Remind about donations or not", - help => "", + name => 'ZM_DYN_SHOW_DONATE_REMINDER', + default => 'yes', + description => 'Remind about donations or not', + help => '', type => $types{boolean}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_SSMTP_MAIL", - default => "no", - description => qqq(" - Use a SSMTP mail server if available. - NEW_MAIL_MODULES must be enabled - "), + name => 'ZM_SSMTP_MAIL', + default => 'no', + description => q` + Use a SSMTP mail server if available. + NEW_MAIL_MODULES must be enabled + `, requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" }, - { name => "ZM_NEW_MAIL_MODULES", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' }, + { name => 'ZM_NEW_MAIL_MODULES', value => 'yes' } ], - help => qqq(" - 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: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder - for setup and configuration help. - "), + help => q` + 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: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder + for setup and configuration help. + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_SSMTP_PATH", - default => "", - description => "SSMTP executable path", - requires => [{ name => "ZM_SSMTP_MAIL", value => "yes" }], - help => qqq(" - Recommend setting 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. - "), + name => 'ZM_SSMTP_PATH', + default => '', + description => 'SSMTP executable path', + requires => [{ name => 'ZM_SSMTP_MAIL', value => 'yes' }], + help => q` + Recommend setting 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. + `, type => $types{string}, - category => "mail", + category => 'mail', }, ); our %options_hash = map { ( $_->{name}, $_ ) } @options; -# This function should never need to be called explicitly, except if +# This function should never need to be called explicitly, except if # this module is 'require'd rather than 'use'd. See zmconfgen.pl. sub initialiseConfig { return if ( $configInitialised ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm index de4d56346..9cf00d077 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm @@ -215,6 +215,55 @@ sub presetHome $self->sendCmd( 'decoder_control.cgi?command=25&' ); } +sub moveRelUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->sendCmd( 'decoder_control.cgi?command=0&onestep=1&' ); +} + +#Down Arrow +sub moveRelDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->sendCmd( 'decoder_control.cgi?command=2&onestep=1&' ); +} + +#Left Arrow +sub moveRelLeft +{ + my $self = shift; + Debug( "Move Left" ); + $self->sendCmd( 'decoder_control.cgi?command=6&onestep=1&' ); +} + +#Right Arrow +sub moveRelRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->sendCmd( 'decoder_control.cgi?command=4&onestep=1&' ); +} + +#Go to preset +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + my $result = undef; + if ( $preset > 0 && $preset <= 32 ) { + my $command=31+(($preset-1) * 2); + Debug( "Goto Preset $preset with command $command" ); + $result=$self->sendCmd( 'decoder_control.cgi?command=' . $command . '&' ); + } + else { + Error( "Unsupported preset $preset : must be between 1 and 32" ); + } + return $result; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index 2c665b4ad..f9cabd5fa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm index 0f7a75012..38838237b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm @@ -1,27 +1,34 @@ +# ========================================================================== +# +# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module, $Date: 2017-03-19 23:00:00 +1000 (Sat, 19 March 2017) $, $Revision: 0002 $ +# Copyright (C) 2001-2008 Philip Coombes +# Modified for use with Foscam FI8918W IP Camera by Dave Harris +# Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: +# fix horizontal panning, add presets and IR on/off +# use Control Device field to pass username and password +# Modified May 2014 by Arun Horne (http://arunhorne.co.uk) to: +# use HTTP basic auth as required by firmware 11.37.x.x upward # Modified on Sep 28 2015 by Bobby Billingsley # Changes made # - Copied FI8918W.pm to SPP1802SWPTZ.pm # - modified to control a SunEyes SP-P1802SWPTZ - -# ========================================================================== -# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module +# Modified on 13 March 2017 by Steve Gilvarry +# -Address license and copyright issues # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -# ========================================================================== -# # This module contains the implementation of the SunEyes SP-P1802SWPTZ IP # camera control protocol # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm index e5917536f..b3f9418ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm @@ -45,265 +45,239 @@ use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); -sub new -{ - my $class = shift; - my $id = shift; - my $self = ZoneMinder::Control->new( $id ); - bless( $self, $class ); - srand( time() ); - return $self; +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; } our $AUTOLOAD; -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); - } - Fatal( "Can't access $name member of object of class $class" ); +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); } -sub open -{ - my $self = shift; +sub open { + my $self = shift; - $self->loadMonitor(); + $self->loadMonitor(); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); - $self->{state} = 'open'; + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); +sub printMsg { + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); - Debug( $msg."[".$msg_len."]" ); + Debug( $msg."[".$msg_len."]" ); } -sub sendCmd -{ - my $self = shift; - my $cmd = shift; +sub sendCmd { + my $self = shift; + my $cmd = shift; - my $result = undef; + my $result = undef; - printMsg( $cmd, "Tx" ); + printMsg( $cmd, "Tx" ); - my $url; - if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { - $url = $self->{Monitor}->{ControlAddress}.$cmd; - } else { - $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; - } # en dif - my $req = HTTP::Request->new( GET=>$url ); + my $url; + if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { + $url = $self->{Monitor}->{ControlAddress}.$cmd; + } else { + $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; + } # en dif + my $req = HTTP::Request->new( GET=>$url ); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->request($req); - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Error check failed: '".$res->status_line()."'" ); - } + if ( $res->is_success ) { + $result = !undef; + } else { + Error( "Error check failed: '".$res->status_line()."'" ); + } - return( $result ); + return( $result ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/admin/ptctl.cgi?move=reset"; - $self->sendCmd( $cmd ); +sub reset { + my $self = shift; + Debug( "Camera Reset" ); + my $cmd = "/admin/ptctl.cgi?move=reset"; + $self->sendCmd( $cmd ); } -sub moveMap -{ - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); +sub moveMap { + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); - my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; - my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; - my $maxver = 8; - my $maxhor = 30; - - my $horDir = "right"; - my $verDir = "up"; - my $horSteps = 0; - my $verSteps = 0; + my $maxver = 8; + my $maxhor = 30; - # Horizontal movement - if ($hor < 50) { - # left - $horSteps = ((50 - $hor) / 50) * $maxhor; - $horDir = "left"; - } - elsif ($hor > 50) { - # right - $horSteps = (($hor - 50) / 50) * $maxhor; - $horDir = "right"; - } - - # Vertical movement - if ($ver < 50) { - # up - $verSteps = ((50 - $ver) / 50) * $maxver; - $verDir = "up"; - } - elsif ($ver > 50) { - # down - $verSteps = (($ver - 50) / 50) * $maxver; - $verDir = "down"; - } + my $horDir = "right"; + my $verDir = "up"; + my $horSteps = 0; + my $verSteps = 0; - my $v = int($verSteps); - my $h = int($horSteps); +# Horizontal movement + if ( $hor < 50 ) { +# left + $horSteps = ((50 - $hor) / 50) * $maxhor; + $horDir = "left"; + } + elsif ( $hor > 50 ) { +# right + $horSteps = (($hor - 50) / 50) * $maxhor; + $horDir = "right"; + } - Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); - my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; - $self->sendCmd( $cmd ); - $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; - $self->sendCmd( $cmd ); +# Vertical movement + if ( $ver < 50 ) { +# up + $verSteps = ((50 - $ver) / 50) * $maxver; + $verDir = "up"; + } + elsif ( $ver > 50 ) { +# down + $verSteps = (($ver - 50) / 50) * $maxver; + $verDir = "down"; + } + + my $v = int($verSteps); + my $h = int($horSteps); + + Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); + my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; + $self->sendCmd( $cmd ); + $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; + $self->sendCmd( $cmd ); } -sub moveRelUp -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/admin/ptctl.cgi?move=up"; - $self->sendCmd( $cmd ); +sub moveRelUp { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Up $step" ); + my $cmd = "/admin/ptctl.cgi?move=up"; + $self->sendCmd( $cmd ); } -sub moveRelDown -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/admin/ptctl.cgi?move=down"; - $self->sendCmd( $cmd ); +sub moveRelDown { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Down $step" ); + my $cmd = "/admin/ptctl.cgi?move=down"; + $self->sendCmd( $cmd ); } -sub moveRelLeft -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Left $step" ); - my $cmd = "/admin/ptctl.cgi?move=left"; - $self->sendCmd( $cmd ); +sub moveRelLeft { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Right because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } else { + Debug( "Step Left" ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } } -sub moveRelRight -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Right $step" ); - my $cmd = "/admin/ptctl.cgi?move=right"; - $self->sendCmd( $cmd ); +sub moveRelRight { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Left because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } else { + Debug( "Step Right" ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } } -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); - #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; - #$self->sendCmd( $cmd ); +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); +#my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; +#$self->sendCmd( $cmd ); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; - $self->sendCmd( $cmd ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; + $self->sendCmd( $cmd ); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); - $self->sendCmd( $cmd ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); + $self->sendCmd( $cmd ); } -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/admin/ptctl.cgi?move=h"; - $self->sendCmd( $cmd ); +sub presetHome { + my $self = shift; + Debug( "Home Preset" ); + my $cmd = "/admin/ptctl.cgi?move=h"; + $self->sendCmd( $cmd ); } 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Control::SkyIPCam7xx.pm - Module for controlling AirLink101 SkyIPams =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Control::SkyIPCam7xx; =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. +Module for controlling AirLink101 Cameras. =head2 EXPORT None by default. - - =head1 SEE ALSO -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +ZoneMinder::Control =head1 AUTHOR @@ -318,5 +292,4 @@ This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. - =cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 9e8e041c8..19374543e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -75,9 +75,11 @@ sub zmDbConnect { if ( $force ) { zmDbDisconnect(); } - if ( !defined( $dbh ) ) { + my $options = shift; + + if ( ( ! defined( $dbh ) ) or ! $dbh->ping() ) { + my ( $host, $portOrSocket ) = ( $ZoneMinder::Config::Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { @@ -89,7 +91,7 @@ sub zmDbConnect { $socket = ";host=".$Config{ZM_DB_HOST}; } $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket + .$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 71ca57680..d616d893f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -28,30 +28,12 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; +require ZoneMinder::Object; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] - ); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +#our @ISA = qw(ZoneMinder::Object); +use parent qw(ZoneMinder::Object); # ========================================================================== # @@ -62,39 +44,24 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require Date::Parse; + +use vars qw/ $table $primary_key /; +$table = 'Events'; +$primary_key = 'Id'; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); +sub Time { + if ( @_ > 1 ) { + $_[0]{Time} = $_[1]; } - return $self; -} # end sub new + if ( ! defined $_[0]{Time} ) { -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Event $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + } + return $_[0]{Time}; +} sub Name { if ( @_ > 1 ) { @@ -130,6 +97,7 @@ sub find { my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while + $sth->finish(); return @results; } @@ -138,36 +106,51 @@ sub find_one { return $results[0] if @results; } -sub getEventPath { +sub getPath { + return Path( @_ ); +} +sub Path { my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } else { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; + if ( @_ > 1 ) { + $$event{Path} = $_[1]; + if ( ! -e $$event{Path} ) { + Error("Setting path for event $$event{Id} to $_[1] but does not exist!"); + } } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( ! $$event{Path} ) { + my $path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( $event->Time() ) { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->Time()) + ), + ); + } else { + Error("Event $$event{Id} has no value for Time(), unable to determine path"); + $$event{Path} = ''; + } + } else { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + $event->{Id}, + ); + } + } # end if + + return $$event{Path}; } sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = getEventPath( $self ); + my $event_path = $self->getPath( ); chdir( $event_path ); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; @@ -228,9 +211,7 @@ sub GenerateVideo { my $command = $Config{ZM_PATH_FFMPEG} ." -y -r $frame_rate " .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " + .' -i ' . ( $$self{DefaultVideo} ? $$self{DefaultVideo} : '%0'.$Config{ZM_EVENT_IMAGE_DIGITS} .'d-capture.jpg' ) #. " -f concat -i /tmp/event_files.txt" ." -s $video_size " .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} @@ -253,54 +234,143 @@ sub GenerateVideo { Info( "Video file $video_file already exists for event $self->{Id}\n" ); return $event_path.'/'.$video_file; } - return; + return; } # end sub GenerateVideo +sub delete { + my $event = $_[0]; + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} $event->{StartTime}\n" ); + $ZoneMinder::Database::dbh->ping(); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $event->delete_files( ); + } else { + Debug('Not deleting frames, stats and files for speed.'); + } +} # end sub delete + + +sub delete_files { + + my $storage_path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( ! $storage_path ) { + Fatal("Empty path when deleting files for event $_[0]{Id} "); + return; + } + + chdir ( $storage_path ); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( ! $_[0]{MonitorId} ) { + Error("No monitor id assigned to event $_[0]{Id}"); + return; + } + Debug("Deleting files for Event $_[0]{Id} from $storage_path."); + my $link_path = $_[0]{MonitorId}.'/*/*/*/.'.$_[0]{Id}; +#Debug( "LP1:$link_path" ); + my @links = glob($link_path); +#Debug( "L:".$links[0].": $!" ); + if ( @links ) { + ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint +#Debug( "LP2:$link_path" ); + + ( my $day_path = $link_path ) =~ s/\.\d+//; +#Debug( "DP:$day_path" ); + my $event_path = $day_path.readlink( $link_path ); + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint +#Debug( "EP:$event_path" ); + my $command = "/bin/rm -rf $event_path"; +#Debug( "C:$command" ); + ZoneMinder::General::executeShellCommand( $command ); + + unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); + + my @path_parts = split( /\//, $event_path ); + for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) { + my $delete_path = join( '/', @path_parts[0..$i] ); +#Debug( "DP$i:$delete_path" ); + my @has_files = glob( join('/', $storage_path,$delete_path,'*' ) ); +#Debug( "HF1:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + @has_files = glob( join('/', $storage_path, $delete_path, '.[0-9]*' ) ); +#Debug( "HF2:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + my $command = "/bin/rm -rf $storage_path/$delete_path"; + ZoneMinder::General::executeShellCommand( $command ); + } + } + } else { + my $command = "/bin/rm -rf $storage_path/$_[0]{MonitorId}/$_[0]{Id}"; + ZoneMinder::General::executeShellCommand( $command ); + } +} # end sub delete_files + +sub Storage { + return new ZoneMinder::Storage( $_[0]{StorageId} ); +} + +sub check_for_in_filesystem { + my $path = $_[0]->Path(); + if ( $path ) { + my @files = glob( $path . '/*' ); +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); + return 1 if @files; + } +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found no files"); + return 0; +} + +sub age { + if ( ! $_[0]{age} ) { + $_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60))); + } + return $_[0]{age}; +} + 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Event - Perl Class for events =head1 SYNOPSIS use ZoneMinder::Event; -blah blah blah =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. - -=head2 EXPORT - -None by default. - - - -=head1 SEE ALSO - -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +The Event class has everything you need to deal with events from Perl. =head1 AUTHOR -Philip Coombes, Ephilip.coombes@zoneminder.comE +Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE -Copyright (C) 2001-2008 Philip Coombes +Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 22bcb0062..75d0e6ad4 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -28,31 +28,14 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +use parent qw(ZoneMinder::Object); +use vars qw/ $table $primary_key /; +$table = 'Filters'; +$primary_key = 'Id'; # ========================================================================== # # General Utility Functions @@ -62,40 +45,10 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require ZoneMinder::Server; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; -} # end sub new - -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Filter $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load - sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; @@ -130,6 +83,8 @@ sub find { my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while + $sth->finish(); + return @results; } @@ -140,7 +95,6 @@ sub find_one { sub Execute { my $self = $_[0]; - my $sql = $self->Sql(); if ( $self->{HasDiskPercent} ) { @@ -156,8 +110,9 @@ sub Execute { $sql =~ s/zmSystemLoad/$load/g; } - my $sth = $$self{dbh}->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); + Debug("Filter::Execute SQL ($sql)"); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute(); if ( !$res ) { Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); @@ -169,6 +124,7 @@ sub Execute { push @results, $event; } $sth->finish(); + Debug("Loaded " . @results . " events for filter $_[0]{Name} using query ($sql)"); return @results; } @@ -176,78 +132,74 @@ sub Sql { my $self = $_[0]; if ( ! $$self{Sql} ) { my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); - my $sql = "SELECT E.Id, - E.MonitorId, + my $sql = "SELECT E.*, + unix_timestamp(E.StartTime) as Time, M.Name as MonitorName, M.DefaultRate, - M.DefaultScale, - E.Name, - E.Cause, - E.Notes, - E.StartTime, - unix_timestamp(E.StartTime) as Time, - E.Length, - E.Frames, - E.AlarmFrames, - E.TotScore, - E.AvgScore, - E.MaxScore, - E.Archived, - E.Videoed, - E.Uploaded, - E.Emailed, - E.Messaged, - E.Executed + M.DefaultScale FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId "; $self->{Sql} = ''; if ( $filter_expr->{terms} ) { - for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { - if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; + foreach my $term ( @{$filter_expr->{terms}} ) { + + if ( exists($term->{cnj}) ) { + $self->{Sql} .= " ".$term->{cnj}." "; } - if ( exists($filter_expr->{terms}[$i]->{obr}) ) { - $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; + if ( exists($term->{obr}) ) { + $self->{Sql} .= " ".str_repeat( "(", $term->{obr} )." "; } - my $value = $filter_expr->{terms}[$i]->{val}; + my $value = $term->{val}; my @value_list; - if ( $filter_expr->{terms}[$i]->{attr} ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; + if ( $term->{attr} ) { + if ( $term->{attr} =~ /^Monitor/ ) { + my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= "M.".$temp_attr_name; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + } elsif ( $term->{attr} =~ /^Server/ ) { + $self->{Sql} .= "M.".$term->{attr}; + } elsif ( $term->{attr} eq 'DateTime' ) { $self->{Sql} .= "E.StartTime"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + } elsif ( $term->{attr} eq 'Date' ) { $self->{Sql} .= "to_days( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + } elsif ( $term->{attr} eq 'Time' ) { $self->{Sql} .= "extract( hour_second from E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { + } elsif ( $term->{attr} eq 'Weekday' ) { $self->{Sql} .= "weekday( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { + } elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= "zmDiskPercent"; $self->{HasDiskPercent} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { + } elsif ( $term->{attr} eq 'DiskBlocks' ) { $self->{Sql} .= "zmDiskBlocks"; $self->{HasDiskBlocks} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { + } elsif ( $term->{attr} eq 'SystemLoad' ) { $self->{Sql} .= "zmSystemLoad"; $self->{HasSystemLoad} = !undef; } else { - $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; + $self->{Sql} .= "E.".$term->{attr}; } ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + if ( $term->{attr} =~ /^Monitor/ ) { $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' - || $filter_expr->{terms}[$i]->{attr} eq 'Cause' - || $filter_expr->{terms}[$i]->{attr} eq 'Notes' + } elsif ( $term->{attr} eq 'ServerId' ) { + if ( $temp_value eq 'ZM_SERVER_ID' ) { + $value = "'$Config{ZM_SERVER_ID}'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $Config{ZM_SERVER_ID} ); + } else { + $value = "'$temp_value'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $temp_value ); + } + } elsif ( $term->{attr} eq 'Name' + || $term->{attr} eq 'Cause' + || $term->{attr} eq 'Notes' ) { $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + } elsif ( $term->{attr} eq 'DateTime' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -255,7 +207,7 @@ sub Sql { return; } $value = "'$value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + } elsif ( $term->{attr} eq 'Date' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -263,7 +215,7 @@ sub Sql { return; } $value = "to_days( '$value' )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + } elsif ( $term->{attr} eq 'Time' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -277,21 +229,21 @@ sub Sql { push( @value_list, $value ); } # end foreach temp_value } # end if has an attr - if ( $filter_expr->{terms}[$i]->{op} ) { - if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { + if ( $term->{op} ) { + if ( $term->{op} eq '=~' ) { $self->{Sql} .= " regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { + } elsif ( $term->{op} eq '=[]' ) { $self->{Sql} .= " in (".join( ",", @value_list ).")"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not in (".join( ",", @value_list ).")"; } else { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; + $self->{Sql} .= " ".$term->{op}." $value"; } } # end if has an operator - if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { - $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; + if ( exists($term->{cbr}) ) { + $self->{Sql} .= " ".str_repeat( ")", $term->{cbr} )." "; } } # end foreach term } # end if terms @@ -308,22 +260,24 @@ sub Sql { } my @auto_terms; if ( $self->{AutoArchive} ) { - push( @auto_terms, "E.Archived = 0" ) + push @auto_terms, "E.Archived = 0"; } + # Don't do this, it prevents re-generation and concatenation. + # If the file already exists, then the video won't be re-recreated if ( $self->{AutoVideo} ) { - push( @auto_terms, "E.Videoed = 0" ) + push @auto_terms, "E.Videoed = 0"; } if ( $self->{AutoUpload} ) { - push( @auto_terms, "E.Uploaded = 0" ) + push @auto_terms, "E.Uploaded = 0"; } if ( $self->{AutoEmail} ) { - push( @auto_terms, "E.Emailed = 0" ) + push @auto_terms, "E.Emailed = 0"; } if ( $self->{AutoMessage} ) { - push( @auto_terms, "E.Messaged = 0" ) + push @auto_terms, "E.Messaged = 0"; } if ( $self->{AutoExecute} ) { - push( @auto_terms, "E.Executed = 0" ) + push @auto_terms, "E.Executed = 0"; } if ( @auto_terms ) { $sql .= " and ( ".join( " or ", @auto_terms )." )"; @@ -361,14 +315,13 @@ sub Sql { if ( $filter_expr->{limit} ) { $sql .= " limit 0,".$filter_expr->{limit}; } - Debug( "SQL:$sql\n" ); $self->{Sql} = $sql; } # end if has Sql return $self->{Sql}; } # end sub Sql sub getDiskPercent { - my $command = "df ."; + my $command = "df " . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)%/ms ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index bb03fe70c..3d004725b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -103,8 +103,10 @@ sub getCmdFormat { my $suffix = ""; my $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - my $output = qx($command); + my $output = qx($command 2>&1); my $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); @@ -116,8 +118,10 @@ sub getCmdFormat { $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - my $output = qx($command); + my $output = qx($command 2>&1); my $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); @@ -129,8 +133,10 @@ sub getCmdFormat { $suffix = "'"; $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - $output = qx($command); + $output = qx($command 2>&1); $status = $? >> 8; + $output //= $!; + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 286d83db1..91f2d0b54 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -41,7 +41,7 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'constants' => [ qw( + constants => [ qw( STATE_IDLE STATE_PREALARM STATE_ALARM @@ -56,7 +56,7 @@ our %EXPORT_TAGS = ( TRIGGER_ON TRIGGER_OFF ) ], - 'functions' => [ qw( + functions => [ qw( zmMemVerify zmMemInvalidate zmMemRead @@ -77,12 +77,12 @@ our %EXPORT_TAGS = ( zmTriggerEventOn zmTriggerEventOff zmTriggerEventCancel - zmTriggerShowtext + zmTriggerShowtext ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); @@ -115,7 +115,7 @@ use constant TRIGGER_OFF => 2; use Storable qw( freeze thaw ); -if ( "@ENABLE_MMAP@" eq 'yes' ) { +if ( '@ENABLE_MMAP@' eq 'yes' ) { # 'yes' if memory is mmapped require ZoneMinder::Memory::Mapped; ZoneMinder::Memory::Mapped->import(); @@ -141,46 +141,45 @@ our $native = $arch/8; our $mem_seq = 0; our $mem_data = { - "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, + shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + last_write_index => { type=>'uint32', seq=>$mem_seq++ }, + last_read_index => { type=>'uint32', seq=>$mem_seq++ }, + state => { type=>'uint32', seq=>$mem_seq++ }, + last_event => { type=>'uint32', seq=>$mem_seq++ }, + action => { type=>'uint32', seq=>$mem_seq++ }, + brightness => { type=>'int32', seq=>$mem_seq++ }, + hue => { type=>'int32', seq=>$mem_seq++ }, + colour => { type=>'int32', seq=>$mem_seq++ }, + contrast => { type=>'int32', seq=>$mem_seq++ }, + alarm_x => { type=>'int32', seq=>$mem_seq++ }, + alarm_y => { type=>'int32', seq=>$mem_seq++ }, + valid => { type=>'uint8', seq=>$mem_seq++ }, + active => { type=>'uint8', seq=>$mem_seq++ }, + signal => { type=>'uint8', seq=>$mem_seq++ }, + format => { type=>'uint8', seq=>$mem_seq++ }, + imagesize => { type=>'uint32', seq=>$mem_seq++ }, + epadding1 => { type=>'uint32', seq=>$mem_seq++ }, + epadding2 => { type=>'uint32', seq=>$mem_seq++ }, + last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, + last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, + control_state => { type=>'uint8[256]', seq=>$mem_seq++ }, } }, - "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, - "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, + trigger_data => { type=>'TriggerData', seq=>$mem_seq++, 'contents'=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + trigger_state => { type=>'uint32', seq=>$mem_seq++ }, + trigger_score => { type=>'uint32', seq=>$mem_seq++ }, + padding => { type=>'uint32', seq=>$mem_seq++ }, + trigger_cause => { type=>'int8[32]', seq=>$mem_seq++ }, + trigger_text => { type=>'int8[256]', seq=>$mem_seq++ }, + trigger_showtext => { type=>'int8[256]', seq=>$mem_seq++ }, } }, - "end" => { "seq"=>$mem_seq++, "size"=> 0 } + end => { seq=>$mem_seq++, size=>0 } }; our $mem_size = 0; -our $mem_verified = {}; sub zmMemInit { my $offset = 0; @@ -196,28 +195,28 @@ sub zmMemInit { } } foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) { - if ( $member_data->{type} eq "long" - || $member_data->{type} eq "ulong" - || $member_data->{type} eq "size_t" + if ( $member_data->{type} eq 'long' + || $member_data->{type} eq 'ulong' + || $member_data->{type} eq 'size_t' ) { $member_data->{size} = $member_data->{align} = $native; - } elsif ( $member_data->{type} eq "int64" - || $member_data->{type} eq "uint64" - || $member_data->{type} eq "time_t64" + } elsif ( $member_data->{type} eq 'int64' + || $member_data->{type} eq 'uint64' + || $member_data->{type} eq 'time_t64' ) { $member_data->{size} = $member_data->{align} = 8; - } elsif ( $member_data->{type} eq "int32" - || $member_data->{type} eq "uint32" - || $member_data->{type} eq "bool4" + } elsif ( $member_data->{type} eq 'int32' + || $member_data->{type} eq 'uint32' + || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; - } elsif ($member_data->{type} eq "int16" - || $member_data->{type} eq "uint16" + } elsif ($member_data->{type} eq 'int16' + || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; - } elsif ( $member_data->{type} eq "int8" - || $member_data->{type} eq "uint8" - || $member_data->{type} eq "bool1" + } elsif ( $member_data->{type} eq 'int8' + || $member_data->{type} eq 'uint8' + || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { @@ -249,51 +248,52 @@ sub zmMemVerify { return( undef ); } - my $mem_key = zmMemKey( $monitor ); - if ( !defined($mem_verified->{$mem_key}) ) { - my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); - if ( $sd_size != $mem_data->{shared_data}->{size} ) { - if ( $sd_size ) { - Error( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got " - .$sd_size - ); - } else { - Debug( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got ".$sd_size - ); - } - return( undef ); + my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); + if ( $sd_size != $mem_data->{shared_data}->{size} ) { + if ( $sd_size ) { + Error( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got " + .$sd_size + ); + } else { + Debug( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got ".$sd_size + ); } - my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); - if ( $td_size != $mem_data->{trigger_data}->{size} ) { - if ( $td_size ) { - Error( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } else { - Debug( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - return( undef ); - } - $mem_verified->{$mem_key} = !undef; + return( undef ); } + my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); + if ( $td_size != $mem_data->{trigger_data}->{size} ) { + if ( $td_size ) { + Error( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } else { + Debug( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } + return( undef ); + } + if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { + Error( "Shared data not valid for monitor $$monitor{Id}" ); + return( undef ); + } + return( !undef ); } @@ -325,32 +325,32 @@ sub zmMemRead { return( undef ); } my $value; - if ( $type eq "long" ) { - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int32" ) { - ( $value ) = unpack( "l", $data ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - ( $value ) = unpack( "L", $data ); - } elsif ( $type eq "int16" ) { - ( $value ) = unpack( "s", $data ); - } elsif ( $type eq "uint16" ) { - ( $value ) = unpack( "S", $data ); - } elsif ( $type eq "int8" ) { - ( $value ) = unpack( "c", $data ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - ( $value ) = unpack( "C", $data ); + if ( $type eq 'long' ) { + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int32' ) { + ( $value ) = unpack( 'l', $data ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + ( $value ) = unpack( 'L', $data ); + } elsif ( $type eq 'int16' ) { + ( $value ) = unpack( 's', $data ); + } elsif ( $type eq 'uint16' ) { + ( $value ) = unpack( 'S', $data ); + } elsif ( $type eq 'int8' ) { + ( $value ) = unpack( 'c', $data ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + ( $value ) = unpack( 'C', $data ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - ( $value ) = unpack( "Z".$size, $data ); + ( $value ) = unpack( 'Z'.$size, $data ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - ( $value ) = unpack( "C".$size, $data ); + ( $value ) = unpack( 'C'.$size, $data ); } else { Fatal( "Unexpected type '".$type."' found for '".$field."'" ); } @@ -366,7 +366,6 @@ sub zmMemInvalidate { my $monitor = shift; my $mem_key = zmMemKey($monitor); if ( $mem_key ) { - delete $mem_verified->{$mem_key}; zmMemDetach( $monitor ); } } @@ -395,34 +394,34 @@ sub zmMemWrite { my $size = $mem_data->{$section}->{contents}->{$element}->{size}; my $data; - if ( $type eq "long" ) { - $data = pack( "l!", $value ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - $data = pack( "L!", $value ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "l!", $value ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "L!", $value ); - } elsif ( $type eq "int32" ) { - $data = pack( "l", $value ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - $data = pack( "L", $value ); - } elsif ( $type eq "int16" ) { - $data = pack( "s", $value ); - } elsif ( $type eq "uint16" ) { - $data = pack( "S", $value ); - } elsif ( $type eq "int8" ) { - $data = pack( "c", $value ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - $data = pack( "C", $value ); + if ( $type eq 'long' ) { + $data = pack( 'l!', $value ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'l!', $value ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int32' ) { + $data = pack( 'l', $value ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + $data = pack( 'L', $value ); + } elsif ( $type eq 'int16' ) { + $data = pack( 's', $value ); + } elsif ( $type eq 'uint16' ) { + $data = pack( 'S', $value ); + } elsif ( $type eq 'int8' ) { + $data = pack( 'c', $value ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + $data = pack( 'C', $value ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - $data = pack( "Z".$size, $value ); + $data = pack( 'Z'.$size, $value ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - $data = pack( "C".$size, $value ); + $data = pack( 'C'.$size, $value ); } else { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); + Fatal( "Unexpected type \"$type\" found for \"$field\"" ); } if ( !zmMemPut( $monitor, $offset, $size, $data ) ) { @@ -439,26 +438,26 @@ sub zmMemWrite { sub zmGetMonitorState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:state" ) ); + return( zmMemRead( $monitor, 'shared_data:state' ) ); } sub zmGetAlarmLocation { my $monitor = shift; - return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); + return( zmMemRead( $monitor, [ 'shared_data:alarm_x', 'shared_data:alarm_y' ] ) ); } sub zmSetControlState { my $monitor = shift; my $control_state = shift; - zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); + zmMemWrite( $monitor, { 'shared_data:control_state' => $control_state } ); } sub zmGetControlState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:control_state" ) ); + return( zmMemRead( $monitor, 'shared_data:control_state' ) ); } sub zmSaveControlState { @@ -494,8 +493,8 @@ sub zmHasAlarmed { my $monitor = shift; my $last_event_id = shift; - my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" - ,"shared_data:last_event" + my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state' + ,'shared_data:last_event' ] ); @@ -510,63 +509,63 @@ sub zmHasAlarmed { sub zmGetLastEvent { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_event" ) ); + return( zmMemRead( $monitor, 'shared_data:last_event' ) ); } sub zmGetLastWriteTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_write_time' ) ); } sub zmGetLastReadTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_read_time' ) ); } sub zmGetMonitorActions { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:action" ) ); + return( zmMemRead( $monitor, 'shared_data:action' ) ); } sub zmMonitorEnable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorDisable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorSuspend { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorResume { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmGetTriggerState { my $monitor = shift; - return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); + return( zmMemRead( $monitor, 'trigger_data:trigger_state' ) ); } sub zmTriggerEventOn { @@ -577,12 +576,12 @@ sub zmTriggerEventOn { my $showtext = shift; my $values = { - "trigger_data:trigger_score" => $score, - "trigger_data:trigger_cause" => $cause, + 'trigger_data:trigger_score' => $score, + 'trigger_data:trigger_cause' => $cause, }; - $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); - $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); - $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete + $values->{'trigger_data:trigger_text'} = $text if ( defined($text) ); + $values->{'trigger_data:trigger_showtext'} = $showtext if ( defined($showtext) ); + $values->{'trigger_data:trigger_state'} = TRIGGER_ON; # Write state last so event not read incomplete zmMemWrite( $monitor, $values ); } @@ -591,11 +590,11 @@ sub zmTriggerEventOff { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_OFF, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_OFF, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -605,11 +604,11 @@ sub zmTriggerEventCancel { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_CANCEL, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_CANCEL, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -620,7 +619,7 @@ sub zmTriggerShowtext { my $showtext = shift; my $values = { - "trigger_data:trigger_showtext" => $showtext, + 'trigger_data:trigger_showtext' => $showtext, }; zmMemWrite( $monitor, $values ); @@ -649,7 +648,7 @@ if ( zmMemVerify( $monitor ) ) { "shared_data:last_write_index" ] ); -zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); +zmMemWrite( $monitor, { 'trigger_data:trigger_showtext' => "Some Text" } ); =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index e41019647..bd826c9c7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -40,15 +40,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'functions' => [ qw( - zmMemKey - zmMemAttach - zmMemDetach - zmMemGet - zmMemPut - zmMemClean - ) ], -); + functions => [ qw( + zmMemKey + zmMemAttach + zmMemDetach + zmMemGet + zmMemPut + zmMemClean + ) ], + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -68,133 +68,119 @@ use ZoneMinder::Logger qw(:all); use Sys::Mmap; -sub zmMemKey -{ - my $monitor = shift; - return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); +sub zmMemKey { + my $monitor = shift; + return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); } -sub zmMemAttach -{ - my ( $monitor, $size ) = @_; - if ( ! $size ) { - Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); - return( undef ); +sub zmMemAttach { + my ( $monitor, $size ) = @_; + if ( ! $size ) { + Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); + return( undef ); + } + if ( !defined($monitor->{MMapAddr}) ) { + + my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; + if ( ! -e $mmap_file ) { + Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." + , $mmap_file + ) + ); + return ( undef ); } - if ( !defined($monitor->{MMapAddr}) ) - { - my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; - if ( ! -e $mmap_file ) { - Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." - , $mmap_file - ) - ); - return ( undef ); - } + my $mmap_file_size = -s $mmap_file; - my $mmap_file_size = -s $mmap_file; - - if ( $mmap_file_size < $size ) { - Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" - , $mmap_file - , $size - , $mmap_file_size - ) - ); - return ( undef ); - } - if ( !open( MMAP, "+<", $mmap_file ) ) - { - Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); - return( undef ); - } - my $mmap = undef; - my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, \*MMAP ); - if ( !$mmap_addr || !$mmap ) - { - Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); - close( MMAP ); - return( undef ); - } - $monitor->{MMapHandle} = \*MMAP; - $monitor->{MMapAddr} = $mmap_addr; - $monitor->{MMap} = \$mmap; + if ( $mmap_file_size < $size ) { + Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" + , $mmap_file + , $size + , $mmap_file_size + ) + ); + return ( undef ); } - return( !undef ); + my $MMAP; + if ( !open( $MMAP, '+<', $mmap_file ) ) { + Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); + return( undef ); + } + my $mmap = undef; + my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); + if ( !$mmap_addr || !$mmap ) { + Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); + close( $MMAP ); + return( undef ); + } + $monitor->{MMapHandle} = $MMAP; + $monitor->{MMapAddr} = $mmap_addr; + $monitor->{MMap} = \$mmap; + } + return( !undef ); } -sub zmMemDetach -{ - my $monitor = shift; +sub zmMemDetach { + my $monitor = shift; - if ( $monitor->{MMap} ) - { - if ( ! munmap( ${$monitor->{MMap}} ) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); - } - delete $monitor->{MMap}; - } - if ( $monitor->{MMapAddr} ) - { - delete $monitor->{MMapAddr}; - } - if ( $monitor->{MMapHandle} ) - { - close( $monitor->{MMapHandle} ); - delete $monitor->{MMapHandle}; + if ( $monitor->{MMap} ) { + if ( ! munmap( ${$monitor->{MMap}} ) ) { + Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } + delete $monitor->{MMap}; + } + if ( $monitor->{MMapAddr} ) { + delete $monitor->{MMapAddr}; + } + if ( $monitor->{MMapHandle} ) { + close( $monitor->{MMapHandle} ); + delete $monitor->{MMapHandle}; + } } -sub zmMemGet -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; +sub zmMemGet { + my $monitor = shift; + my $offset = shift; + my $size = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - my $data = substr( $$mmap, $offset, $size ); - return( $data ); + return( undef ); + } + my $data = substr( $$mmap, $offset, $size ); + return( $data ); } -sub zmMemPut -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; - my $data = shift; +sub zmMemPut { + my $monitor = shift; + my $offset = shift; + my $size = shift; + my $data = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - substr( $$mmap, $offset, $size ) = $data; - return( !undef ); + return( undef ); + } + substr( $$mmap, $offset, $size ) = $data; + return( !undef ); } -sub zmMemClean -{ - Debug( "Removing memory map files\n" ); - my $mapPath = $Config{ZM_PATH_MAP}."/zm.mmap.*"; - foreach my $mapFile( glob( $mapPath ) ) - { - ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug( "Removing memory map file '$mapFile'\n" ); - unlink( $mapFile ); - } +sub zmMemClean { + Debug( "Removing memory map files\n" ); + my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; + foreach my $mapFile( glob( $mapPath ) ) { + ( $mapFile ) = $mapFile =~ /^(.+)$/; + Debug( "Removing memory map file '$mapFile'\n" ); + unlink( $mapFile ); + } } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm new file mode 100644 index 000000000..d5b7d7f53 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -0,0 +1,85 @@ +# ========================================================================== +# +# ZoneMinder Monitor Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Server; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; +use vars qw/ $table $primary_key /; +$table = 'Monitors'; +$primary_key = 'Id'; + +sub Server { + return new ZoneMinder::Server( $_[0]{ServerId} ); +} # end sub Server + +1; +__END__ + +=head1 NAME + +ZoneMinder::Monitor - Perl Class for Monitors + +=head1 SYNOPSIS + +use ZoneMinder::Monitor; + +=head1 DESCRIPTION + + + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm new file mode 100644 index 000000000..b87e111a6 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -0,0 +1,150 @@ +# ========================================================================== +# +# ZoneMinder Object Module, $Date$, $Revision$ +# Copyright (C) 2001-2017 ZoneMinder LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Object; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; + +our @ISA = qw(ZoneMinder::Base); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use vars qw/ $AUTOLOAD /; + +sub new { + my ( $parent, $id, $data ) = @_; + + my $self = {}; + bless $self, $parent; + no strict 'refs'; + my $primary_key = ${$parent.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $parent ); + return; + } # end if + if ( ( $$self{$primary_key} = $id ) or $data ) { +#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; + $self->load( $data ); + } + return $self; +} # end sub new + +sub load { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { + no strict 'refs'; + my $table = ${$type.'::table'}; + if ( ! $table ) { + Error( 'NO table for type ' . $type ); + return; + } # end if + my $primary_key = ${$type.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $type ); + return; + } # end if + + if ( ! $$self{$primary_key} ) { + my ( $caller, undef, $line ) = caller; + Error( (ref $self) . "::load called without $primary_key from $caller:$line"); + } else { +#$log->debug("Object::load Loading from db $type"); + Debug("Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + $data = $ZoneMinder::Database::dbh->selectrow_hashref( "SELECT * FROM $table WHERE $primary_key=?", {}, $$self{$primary_key} ); + if ( ! $data ) { + if ( $ZoneMinder::Database::dbh->errstr ) { + Error( "Failure to load Object record for $$self{$primary_key}: Reason: " . $ZoneMinder::Database::dbh->errstr ); + } else { + Debug("No Results Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + } # end if + } # end if + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if +} # end sub load + +sub AUTOLOAD { + my ( $self, $newvalue ) = @_; + my $type = ref($_[0]); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( @_ > 1 ) { + return $_[0]{$name} = $_[1]; + } + return $_[0]{$name}; +} + + +1; +__END__ + +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Object + +=head1 SYNOPSIS + + use parent ZoneMinder::Object; + + This package should likely not be used directly, as it is meant mainly to be a parent for all other ZoneMinder classes. + +=head1 DESCRIPTION + + A base Object to act as parent for other ZoneMinder Objects. + +=head2 EXPORT + +None by default. + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index f74f8ed90..6cf2af2e3 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -41,9 +41,9 @@ yet. =head1 OPTIONS - -r, --report - Just report don't actually do anything - -i, --interactive - Ask before applying any changes -c, --continuous - Run continuously + -i, --interactive - Ask before applying any changes + -r, --report - Just report don't actually do anything -v, --version - Print the installed version of ZoneMinder =cut @@ -57,8 +57,8 @@ use bytes; # ========================================================================== use constant MAX_AGED_DIRS => 10; # Number of event dirs to check age on -use constant RECOVER_TAG => "(r)"; # Tag to append to event name when recovered -use constant RECOVER_TEXT => "Recovered."; # Text to append to event notes when recovered +use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered +use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered # ========================================================================== # @@ -96,20 +96,23 @@ logInit(); logSetSignal(); GetOptions( - 'report' =>\$report, - 'interactive' =>\$interactive, - 'continuous' =>\$continuous, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); + continuous =>\$continuous, + interactive =>\$interactive, + report =>\$report, + version =>\$version + ) or pod2usage(-exitstatus => -1); if ( $version ) { - print( ZoneMinder::Base::ZM_VERSION . "\n"); - exit(0); + print( ZoneMinder::Base::ZM_VERSION . "\n"); + exit(0); } -if ( ($report + $interactive + $continuous) > 1 ) -{ - print( STDERR "Error, only one option may be specified\n" ); - pod2usage(-exitstatus => -1); +if ( ($report + $interactive + $continuous) > 1 ) { + print( STDERR "Error, only one option may be specified\n" ); + pod2usage(-exitstatus => -1); +} + +if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { + Fatal('ZM_AUDIT_MIN_AGE is not set in config.'); } my $dbh = zmDbConnect(); @@ -123,517 +126,439 @@ my $image_path = IMAGE_PATH; my $loop = 1; my $cleaned = 0; MAIN: while( $loop ) { - while ( ! ( $dbh and $dbh->ping() ) ) { - $dbh = zmDbConnect(); + while ( ! ( $dbh and $dbh->ping() ) ) { + $dbh = zmDbConnect(); - if ( $continuous ) { - Error("Unable to connect to database"); - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - Fatal("Unable to connect to database"); - } # end if - } # end while can't connect to the db + if ( $continuous ) { + Error('Unable to connect to database'); +# if we are running continuously, then just skip to the next +# interval, otherwise we are a one off run, so wait a second and +# retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + Fatal('Unable to connect to database'); + } # end if + } # end while can't connect to the db - if ( $continuous ) { - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - sleep 1; - } # end if + if ( $continuous ) { +# if we are running continuously, then just skip to the next +# interval, otherwise we are a one off run, so wait a second and +# retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + sleep 1; + } # end if - if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { - Fatal("ZM_AUDIT_MIN_AGE is not set in config."); + my $db_monitors; + my $monitorSelectSql = 'select Id from Monitors order by Id'; + my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) + or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age + FROM Events WHERE MonitorId = ? ORDER BY Id'; + my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) + or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + + $cleaned = 0; + my $res = $monitorSelectSth->execute() + or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); + while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { + Debug( "Found database monitor '$monitor->{Id}'" ); + my $db_events = $db_monitors->{$monitor->{Id}} = {}; + my $res = $eventSelectSth->execute( $monitor->{Id} ) + or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); + while ( my $event = $eventSelectSth->fetchrow_hashref() ) { + $db_events->{$event->{Id}} = $event->{Age}; } + Debug( 'Got '.int(keys(%$db_events))." events\n" ); + } - my $db_monitors; - my $monitorSelectSql = "select Id from Monitors order by Id"; - my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) - or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age - FROM Events WHERE MonitorId = ? ORDER BY Id"; - my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) - or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + my $fs_monitors; + foreach my $monitor ( glob('[0-9]*') ) { +# Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. + next if $monitor =~ /\D/; + Debug( "Found filesystem monitor '$monitor'" ); + my $fs_events = $fs_monitors->{$monitor} = {}; + ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); # De-taint - $cleaned = 0; - my $res = $monitorSelectSth->execute() - or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); - while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) - { - Debug( "Found database monitor '$monitor->{Id}'" ); - my $db_events = $db_monitors->{$monitor->{Id}} = {}; - my $res = $eventSelectSth->execute( $monitor->{Id} ) - or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); - while ( my $event = $eventSelectSth->fetchrow_hashref() ) - { - $db_events->{$event->{Id}} = $event->{Age}; + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) { + Debug( "Checking day dir $day_dir" ); + ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint + if ( ! chdir( $day_dir ) ) { + Error( "Can't chdir to '$day_dir': $!" ); + next; } - Debug( "Got ".int(keys(%$db_events))." events\n" ); - } - - my $fs_monitors; - foreach my $monitor ( glob("[0-9]*") ) - { - # Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. - next if $monitor =~ /\D/; - Debug( "Found filesystem monitor '$monitor'" ); - my $fs_events = $fs_monitors->{$monitor} = {}; - ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); # De-taint - - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) - { - Debug( "Checking $day_dir" ); - ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint - chdir( $day_dir ); - opendir( DIR, "." ) - or Fatal( "Can't open directory '$day_dir': $!" ); - my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event_link ( @event_links ) - { - Debug( "Checking link $event_link" ); - ( my $event = $event_link ) =~ s/^.*\.//; - my $event_path = readlink( $event_link ); - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - if ( !-e $event_path ) - { - aud_print( "Event link $day_dir/$event_link does not point to valid target" ); - if ( confirm() ) - { - ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint - unlink( $event_link ); - $cleaned = 1; - } - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event_path) * 24*60*60))); - } - } - } - chdir( EVENT_PATH ); - } + if ( ! opendir( DIR, '.' ) ) { + Error( "Can't open directory '$day_dir': $!" ); + next; } - else - { - chdir( $monitor_dir ); - opendir( DIR, "." ) or Fatal( "Can't open directory '$monitor_dir': $!" ); - my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event ( @temp_events ) - { - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event) * 24*60*60))); - } - } - chdir( EVENT_PATH ); - } - Debug( "Got ".int(keys(%$fs_events))." events\n" ); - } - redo MAIN if ( $cleaned ); - $cleaned = 0; - while ( my ( $fs_monitor, $fs_events ) = each(%$fs_monitors) ) - { - if ( my $db_events = $db_monitors->{$fs_monitor} ) - { - if ( $fs_events ) - { - while ( my ( $fs_event, $age ) = each(%$fs_events ) ) - { - if ( !defined($db_events->{$fs_event}) && ($age < 0 || ($age > $Config{ZM_AUDIT_MIN_AGE})) ) - { - aud_print( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ); - if ( confirm() ) - { - deleteEventFiles( $fs_event, $fs_monitor ); - $cleaned = 1; - } - } - } - } - } - else - { - aud_print( "Filesystem monitor '$fs_monitor' does not exist in database" ); - if ( confirm() ) - { - my $command = "rm -rf $fs_monitor"; - executeShellCommand( $command ); + my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event_link ( @event_links ) { + Debug( "Checking link $event_link" ); + ( my $event = $event_link ) =~ s/^.*\.//; + my $event_path = readlink( $event_link ); + if ( $count++ > MAX_AGED_DIRS ) { + $fs_events->{$event} = -1; + } else { + if ( !-e $event_path ) { + aud_print( "Event link $day_dir/$event_link does not point to valid target" ); + if ( confirm() ) { + ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint + unlink( $event_link ); $cleaned = 1; + } + } else { + $fs_events->{$event} = (time() - ($^T - ((-M $event_path) * 24*60*60))); } + } + } # end foreach event_link + chdir( EVENT_PATH ); + } # end foreach day_dir + } else { + chdir( $monitor_dir ); + opendir( DIR, "." ) or Fatal( "Can't open directory '$monitor_dir': $!" ); + my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event ( @temp_events ) { + if ( $count++ > MAX_AGED_DIRS ) { + $fs_events->{$event} = -1; + } else { + $fs_events->{$event} = (time() - ($^T - ((-M $event) * 24*60*60))); } + } + chdir( EVENT_PATH ); } + Debug( 'Got '.int(keys(%$fs_events))." events\n" ); + } # end foreach monitor Id + redo MAIN if ( $cleaned ); - my $monitor_links; - foreach my $link ( glob("*") ) - { - next if ( !-l $link ); - next if ( -e $link ); - - aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); - if ( confirm() ) - { - ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint - my $command = qq`rm "$link"`; - executeShellCommand( $command ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - $cleaned = 0; - my $deleteMonitorSql = "delete low_priority from Monitors where Id = ?"; - my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) - or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); - my $deleteEventSql = "delete low_priority from Events where Id = ?"; - my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) - or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); - my $deleteFramesSql = "delete low_priority from Frames where EventId = ?"; - my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) - or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); - my $deleteStatsSql = "delete low_priority from Stats where EventId = ?"; - my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) - or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); - while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) - { - if ( my $fs_events = $fs_monitors->{$db_monitor} ) - { - if ( $db_events ) - { - while ( my ( $db_event, $age ) = each(%$db_events ) ) - { - if ( !defined($fs_events->{$db_event}) ) { - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" ); - if ( confirm() ) { - my $res = $deleteEventSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $res = $deleteFramesSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $res = $deleteStatsSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } else { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem but too young to delete." ); - } - } - } + $cleaned = 0; + while ( my ( $fs_monitor, $fs_events ) = each(%$fs_monitors) ) { + if ( my $db_events = $db_monitors->{$fs_monitor} ) { + if ( $fs_events ) { + while ( my ( $fs_event, $age ) = each(%$fs_events ) ) { + if ( !defined($db_events->{$fs_event}) && ($age < 0 || ($age > $Config{ZM_AUDIT_MIN_AGE})) ) { + aud_print( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ); + if ( confirm() ) { + deleteEventFiles( $fs_event, $fs_monitor ); + $cleaned = 1; } + } } - else - { - aud_print( "Database monitor '$db_monitor' does not exist in filesystem" ); - #if ( confirm() ) - #{ - # We don't actually do this in case it's new - #my $res = $deleteMonitorSth->execute( $db_monitor ) - # or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); - #$cleaned = 1; - #} - } + } + } else { + aud_print( "Filesystem monitor '$fs_monitor' does not exist in database" ); + if ( confirm() ) { + my $command = "rm -rf $fs_monitor"; + executeShellCommand( $command ); + $cleaned = 1; + } } - redo MAIN if ( $cleaned ); + } - # Remove orphaned events (with no monitor) - $cleaned = 0; - my $selectOrphanedEventsSql = "SELECT Events.Id, Events.Name - FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) - WHERE isnull(Monitors.Id)"; - my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) - or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); - $res = $selectOrphanedEventsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); - while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } + my $monitor_links; + foreach my $link ( glob('*') ) { + next if ( !-l $link ); + next if ( -e $link ); + + aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); + if ( confirm() ) { + ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint + my $command = qq`rm "$link"`; + executeShellCommand( $command ); + $cleaned = 1; } - redo MAIN if ( $cleaned ); + } + redo MAIN if ( $cleaned ); - # Remove empty events (with no frames) - $cleaned = 0; - my $selectEmptyEventsSql = "SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) - WHERE isnull(F.EventId) AND now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second > E.StartTime"; - my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) - or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); - $res = $selectEmptyEventsSth->execute() - or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); - while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) - { - aud_print( "Found empty event with no frame records '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned frame records - $cleaned = 0; - my $selectOrphanedFramesSql = "SELECT DISTINCT EventId FROM Frames - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) - or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); - $res = $selectOrphanedFramesSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); - while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteFramesSth->execute( $frame->{EventId} ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned stats records - $cleaned = 0; - my $selectOrphanedStatsSql = "SELECT DISTINCT EventId FROM Stats - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) - or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); - $res = $selectOrphanedStatsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); - while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteStatsSth->execute( $stat->{EventId} ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # New audit to close any events that were left open for longer than MIN_AGE seconds - my $selectUnclosedEventsSql = - "SELECT E.Id, - max(F.TimeStamp) as EndTime, - unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, - max(F.FrameId) as Frames, - count(if(F.Score>0,1,NULL)) as AlarmFrames, - sum(F.Score) as TotScore, - max(F.Score) as MaxScore, - M.EventPrefix as Prefix - FROM Events as E - LEFT JOIN Monitors as M on E.MonitorId = M.Id - INNER JOIN Frames as F on E.Id = F.EventId - WHERE isnull(E.Frames) or isnull(E.EndTime) - GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" - ; - my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) - or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); - my $updateUnclosedEventsSql = - "UPDATE low_priority Events - SET Name = ?, - EndTime = ?, - Length = ?, - Frames = ?, - AlarmFrames = ?, - TotScore = ?, - AvgScore = ?, - MaxScore = ?, - Notes = concat_ws( ' ', Notes, ? ) - WHERE Id = ?" - ; - my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) - or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); - $res = $selectUnclosedEventsSth->execute() - or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); - while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found open event '$event->{Id}'" ); - if ( confirm( 'close', 'closing' ) ) - { - $res = $updateUnclosedEventsSth->execute - ( - sprintf("%s%d%s", - $event->{Prefix}, - $event->{Id}, - RECOVER_TAG - ), - $event->{EndTime}, - $event->{Length}, - $event->{Frames}, - $event->{AlarmFrames}, - $event->{TotScore}, - $event->{AlarmFrames} - ? int($event->{TotScore} / $event->{AlarmFrames}) - : 0 - , - $event->{MaxScore}, - RECOVER_TEXT, - $event->{Id} - ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); - } - } - - # Now delete any old image files - if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) - { - aud_print( "Deleting ".int(@old_files)." old images\n" ); - my $untainted_old_files = join( ";", @old_files ); - ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); - unlink( split( /;/, $untainted_old_files ) ); - } - - # Now delete any old swap files - ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint - File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); - - # Prune the Logs table if required - if ( $Config{ZM_LOG_DATABASE_LIMIT} ) - { - if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) - { - # Number of rows - my $selectLogRowCountSql = "SELECT count(*) as Rows from Logs"; - my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) - or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); - $res = $selectLogRowCountSth->execute() - or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); - my $row = $selectLogRowCountSth->fetchrow_hashref(); - my $logRows = $row->{Rows}; - if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) - { - my $deleteLogByRowsSql = "DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?"; - my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) - or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); - $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) - or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); - if ( $deleteLogByRowsSth->rows() ) - { - aud_print( "Deleted ".$deleteLogByRowsSth->rows() - ." log table entries by count\n" ) - ; - } - } - } - else - { - # Time of record - my $deleteLogByTimeSql = - "DELETE low_priority FROM Logs - WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")"; - my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) - or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); - $res = $deleteLogByTimeSth->execute() - or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); - if ( $deleteLogByTimeSth->rows() ){ - aud_print( "Deleted ".$deleteLogByTimeSth->rows() - ." log table entries by time\n" ) - ; + $cleaned = 0; + my $deleteMonitorSql = 'delete low_priority from Monitors where Id = ?'; + my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) + or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); + my $deleteEventSql = 'delete low_priority from Events where Id = ?'; + my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) + or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); + my $deleteFramesSql = 'delete low_priority from Frames where EventId = ?'; + my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) + or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); + my $deleteStatsSql = 'delete low_priority from Stats where EventId = ?'; + my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) + or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); + while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { + if ( my $fs_events = $fs_monitors->{$db_monitor} ) { + if ( $db_events ) { + while ( my ( $db_event, $age ) = each(%$db_events ) ) { + if ( !defined($fs_events->{$db_event}) ) { + if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { + aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" ); + if ( confirm() ) { + my $res = $deleteEventSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $res = $deleteFramesSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); + $res = $deleteStatsSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); + $cleaned = 1; + } + } else { + aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem but too young to delete." ); } + } } + } # end if db_events + } else { + aud_print( "Database monitor '$db_monitor' does not exist in filesystem" ); +#if ( confirm() ) +#{ +# We don't actually do this in case it's new +#my $res = $deleteMonitorSth->execute( $db_monitor ) +# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); +#$cleaned = 1; +#} } - $loop = $continuous; + } # end foreach db_monitor + redo MAIN if ( $cleaned ); - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; +# Remove orphaned events (with no monitor) + $cleaned = 0; + my $selectOrphanedEventsSql = 'SELECT Events.Id, Events.Name + FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) + WHERE isnull(Monitors.Id)'; + my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) + or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); + $res = $selectOrphanedEventsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); + while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove empty events (with no frames) + $cleaned = 0; + my $selectEmptyEventsSql = "SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) + WHERE isnull(F.EventId) AND now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second > E.StartTime"; + my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) + or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); + $res = $selectEmptyEventsSth->execute() + or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); + while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { + aud_print( "Found empty event with no frame records '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned frame records + $cleaned = 0; + my $selectOrphanedFramesSql = 'SELECT DISTINCT EventId FROM Frames + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) + or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); + $res = $selectOrphanedFramesSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); + while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) { + aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); + if ( confirm() ) { + $res = $deleteFramesSth->execute( $frame->{EventId} ) + or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned stats records + $cleaned = 0; + my $selectOrphanedStatsSql = 'SELECT DISTINCT EventId FROM Stats + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) + or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); + $res = $selectOrphanedStatsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); + while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); + if ( confirm() ) { + $res = $deleteStatsSth->execute( $stat->{EventId} ) + or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# New audit to close any events that were left open for longer than MIN_AGE seconds + my $selectUnclosedEventsSql = + "SELECT E.Id, + max(F.TimeStamp) as EndTime, + unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, + max(F.FrameId) as Frames, + count(if(F.Score>0,1,NULL)) as AlarmFrames, + sum(F.Score) as TotScore, + max(F.Score) as MaxScore, + M.EventPrefix as Prefix + FROM Events as E + LEFT JOIN Monitors as M on E.MonitorId = M.Id + INNER JOIN Frames as F on E.Id = F.EventId + WHERE isnull(E.Frames) or isnull(E.EndTime) + GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" + ; + my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) + or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); + my $updateUnclosedEventsSql = + "UPDATE low_priority Events + SET Name = ?, + EndTime = ?, + Length = ?, + Frames = ?, + AlarmFrames = ?, + TotScore = ?, + AvgScore = ?, + MaxScore = ?, + Notes = concat_ws( ' ', Notes, ? ) + WHERE Id = ?" + ; + my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) + or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); + $res = $selectUnclosedEventsSth->execute() + or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); + while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { + aud_print( "Found open event '$event->{Id}'" ); + if ( confirm( 'close', 'closing' ) ) { + $res = $updateUnclosedEventsSth->execute( + sprintf('%s%d%s', $event->{Prefix}, $event->{Id}, RECOVER_TAG), + $event->{EndTime}, + $event->{Length}, + $event->{Frames}, + $event->{AlarmFrames}, + $event->{TotScore}, + $event->{AlarmFrames} ? int($event->{TotScore} / $event->{AlarmFrames}) : 0, + $event->{MaxScore}, + RECOVER_TEXT, + $event->{Id} + ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + } + } + +# Now delete any old image files + my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}>; + if ( @old_files ) { + aud_print( 'Deleting '.( scalar @old_files )." old images\n" ); + my $untainted_old_files = join( ';', @old_files ); + ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); + unlink( split( /;/, $untainted_old_files ) ); + } + +# Now delete any old swap files + ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint + File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); + + # Prune the Logs table if required + if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { + if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { + # Number of rows + my $selectLogRowCountSql = 'SELECT count(*) AS Rows FROM Logs'; + my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) + or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); + $res = $selectLogRowCountSth->execute() + or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); + my $row = $selectLogRowCountSth->fetchrow_hashref(); + my $logRows = $row->{Rows}; + if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { + my $deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?'; + my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) + or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); + $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) + or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); + if ( $deleteLogByRowsSth->rows() ) { + aud_print( 'Deleted '.$deleteLogByRowsSth->rows() ." log table entries by count\n" ); + } + } + } else { + # Time of record + my $deleteLogByTimeSql = + 'DELETE low_priority FROM Logs + WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')'; + my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) + or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); + $res = $deleteLogByTimeSth->execute() + or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); + if ( $deleteLogByTimeSth->rows() ){ + aud_print( "Deleted ".$deleteLogByTimeSth->rows() ." log table entries by time\n" ); + } + } + } # end if ( $Config{ZM_LOG_DATABASE_LIMIT} ) + $loop = $continuous; + + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; }; exit( 0 ); -sub aud_print -{ - my $string = shift; - if ( !$continuous ) - { - print( $string ); - } - else - { - Info( $string ); - } +sub aud_print { + my $string = shift; + if ( ! $continuous ) { + print( $string ); + } else { + Info( $string ); + } } -sub confirm -{ - my $prompt = shift || "delete"; - my $action = shift || "deleting"; +sub confirm { + my $prompt = shift || 'delete'; + my $action = shift || 'deleting'; - my $yesno = 0; - if ( $report ) - { - print( "\n" ); + my $yesno = 0; + if ( $report ) { + print( "\n" ); + } elsif ( $interactive ) { + print( ", $prompt y/n: " ); + my $char = <>; + chomp( $char ); + if ( $char eq 'q' ) { + exit( 0 ); } - elsif ( $interactive ) - { - print( ", $prompt y/n: " ); - my $char = <>; - chomp( $char ); - if ( $char eq 'q' ) - { - exit( 0 ); - } - if ( !$char ) - { - $char = 'y'; - } - $yesno = ( $char =~ /[yY]/ ); + if ( !$char ) { + $char = 'y'; } - else - { - if ( !$continuous ) - { - print( ", $action\n" ); - } - else - { - Info( $action ); - } - $yesno = 1; + $yesno = ( $char =~ /[yY]/ ); + } else { + if ( !$continuous ) { + print( ", $action\n" ); + } else { + Info( $action ); } - return( $yesno ); + $yesno = 1; + } + return( $yesno ); } -sub deleteSwapImage -{ - my $file = $_; +sub deleteSwapImage { + my $file = $_; - if ( $file !~ /^zmswap-/ ) - { - return; - } + if ( $file !~ /^zmswap-/ ) { + return; + } - # Ignore directories - if ( -d $file ) - { - return; - } +# Ignore directories + if ( -d $file ) { + return; + } - if ( -M $file > $max_swap_age ) - { - Debug( "Deleting $file" ); - #unlink( $file ); - } + if ( -M $file > $max_swap_age ) { + Debug( "Deleting $file" ); +#unlink( $file ); + } } + +1; +__END__ diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 918df8574..33cfde9f7 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -89,7 +89,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmf', 'zmfilter.pl', 'zmaudit.pl', 'zmtrigger.pl', diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index e51993bec..c69ad57b1 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -27,7 +27,7 @@ zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS - zmfilter.pl [-f ,--filter=] | -v, --version +zmfilter.pl [-f ,--filter=] | -v, --version =head1 DESCRIPTION @@ -37,8 +37,8 @@ matching events. =head1 OPTIONS - -f{filter name}, --filter={filter name} - The name of a specific filter to run - -v, --version - Print ZoneMinder version +-f{filter name}, --filter={filter name} - The name of a specific filter to run +-v, --version - Print ZoneMinder version =cut use strict; @@ -60,7 +60,6 @@ use constant START_DELAY => 5; # How long to wait before starting @EXTRA_PERL_LIB@ use ZoneMinder; -require ZoneMinder::Filter; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; @@ -69,61 +68,61 @@ use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); +my $filter_parm = ''; +my $version = 0; + +GetOptions( + 'filter=s' =>\$filter_parm, + 'version' =>\$version + ) or pod2usage(-exitstatus => -1); + +if ( $version ) { + print ZoneMinder::Base::ZM_VERSION . "\n"; + exit(0); +} + +require ZoneMinder::Filter; + use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) -; + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) + ; logInit(); logSetSignal(); -if ( $Config{ZM_OPT_UPLOAD} ) -{ - # Comment these out if you don't have them and don't want to upload - # or don't want to use that format - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - require Archive::Zip; - import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); - } - else - { - require Archive::Tar; - } - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - require Net::FTP; - } - else - { - require Net::SFTP::Foreign; - } +if ( $Config{ZM_OPT_UPLOAD} ) { +# Comment these out if you don't have them and don't want to upload +# or don't want to use that format + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + require Archive::Zip; + import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); + } else { + require Archive::Tar; + } + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + require Net::FTP; + } else { + require Net::SFTP::Foreign; + } } -if ( $Config{ZM_OPT_EMAIL} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_EMAIL} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } -if ( $Config{ZM_OPT_MESSAGE} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_MESSAGE} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } $| = 1; @@ -134,876 +133,795 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -my $filter_parm = ""; -my $version = 0; - -# - -GetOptions( - 'filter=s' =>\$filter_parm, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); - -if ( $version ) { - print ZoneMinder::Base::ZM_VERSION . "\n"; - exit(0); -} if ( ! EVENT_PATH ) { - Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); - die; + Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); + die; } +# In future, should not be neccessary wrt StorageAreas chdir( EVENT_PATH ); +# SHould not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); -if ( $filter_parm ) -{ - Info( "Scanning for events using filter '$filter_parm'\n" ); -} -else -{ - Info( "Scanning for events\n" ); +if ( $filter_parm ) { + Info( "Scanning for events using filter '$filter_parm'\n" ); +} else { + Info( "Scanning for events\n" ); } -if ( !$filter_parm ) -{ - sleep( START_DELAY ); +if ( ! $filter_parm ) { + Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...' ); + sleep( START_DELAY ); } -my $filters; +my @filters; my $last_action = 0; -while( 1 ) -{ - my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) - { - Debug( "Reloading filters\n" ); - $last_action = $now; - $filters = getFilters( $filter_parm ); - } +while( 1 ) { + my $now = time; + if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { + Debug( "Reloading filters\n" ); + $last_action = $now; + @filters = getFilters( $filter_parm ); + } - foreach my $filter ( @$filters ) - { - checkFilter( $filter ); - } + foreach my $filter ( @filters ) { + checkFilter( $filter ); + } - last if ( $filter_parm ); + last if $filter_parm; - Debug( "Sleeping for $delay seconds\n" ); - sleep( $delay ); + Debug( "Sleeping for $delay seconds\n" ); + sleep( $delay ); } -sub getFilters -{ - my $filter_name = shift; +sub getFilters { + my $filter_name = shift; - my @filters; - my $sql = "SELECT * FROM Filters WHERE"; - if ( $filter_name ) - { - $sql .= " Name = ? and"; + my @filters; + my $sql = 'SELECT * FROM Filters WHERE'; + if ( $filter_name ) { + $sql .= ' Name = ? and'; + } else { + $sql .= ' Background = 1 AND'; + } + $sql .= '( AutoArchive = 1 + or AutoVideo = 1 + or AutoUpload = 1 + or AutoEmail = 1 + or AutoMessage = 1 + or AutoExecute = 1 + or AutoDelete = 1 + ) ORDER BY Name'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res; + if ( $filter_name ) { + $res = $sth->execute( $filter_name ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } else { + $res = $sth->execute() + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } +FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + Debug( "Found filter '$db_filter->{Name}'\n" ); + my $sql = $filter->Sql(); + + if ( ! $sql ) { + Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + next FILTER; } - else - { - $sql .= " Background = 1 and"; - } - $sql .= "( AutoArchive = 1 - or AutoVideo = 1 - or AutoUpload = 1 - or AutoEmail = 1 - or AutoMessage = 1 - or AutoExecute = 1 - or AutoDelete = 1 - ) ORDER BY Name"; - my $sth = $dbh->prepare_cached( $sql ) + push( @filters, $filter ); + } + $sth->finish(); + Debug( 'Got ' . @filters . ' filters' ); + return( @filters ); +} + +sub checkFilter { + my $filter = shift; + + Debug( "Checking filter '$filter->{Name}'". + ($filter->{AutoDelete}?', delete':''). + ($filter->{AutoArchive}?', archive':''). + ($filter->{AutoVideo}?', video':''). + ($filter->{AutoUpload}?', upload':''). + ($filter->{AutoEmail}?', email':''). + ($filter->{AutoMessage}?', message':''). + ($filter->{AutoExecute}?', execute':''). + '\n' + ); + + foreach my $event ( $filter->Execute() ) { + Debug( "Checking event $event->{Id}\n" ); + my $delete_ok = !undef; + $dbh->ping(); + if ( $filter->{AutoArchive} ) { + Info( "Archiving event $event->{Id}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'update Events set Archived = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res; - if ( $filter_name ) - { - $res = $sth->execute( $filter_name ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Error( "Can't execute '$sql': ".$sth->errstr() ); } - else - { - $res = $sth->execute() - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { + if ( !$event->{Videoed} ) { + $delete_ok = undef if ( !generateVideo( $filter, $event ) ); + } } - FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) - { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - Debug( "Found filter '$db_filter->{Name}'\n" ); - my $sql = $filter->Sql(); + if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { + if ( !$event->{Emailed} ) { + $delete_ok = undef if ( !sendEmail( $filter, $event ) ); + } + } + if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { + if ( !$event->{Messaged} ) { + $delete_ok = undef if ( !sendMessage( $filter, $event ) ); + } + } + if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { + if ( !$event->{Uploaded} ) { + $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); + } + } + if ( $filter->{AutoExecute} ) { + if ( !$event->{Executed} ) { + $delete_ok = undef if ( !executeCommand( $filter, $event ) ); + } + } + if ( $filter->{AutoDelete} ) { + if ( $delete_ok ) { + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( ! $sql ) { - Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - push( @filters, $filter ); + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + deleteEventFiles( $event->{Id}, $event->{MonitorId} ); + } + } else { + Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); + } + } + } +} + +sub generateVideo { + my $filter = shift; + my $event = shift; + my $phone = shift; + + my $rate = $event->{DefaultRate}/100; + my $scale = $event->{DefaultScale}/100; + my $format; + + my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); + my $default_video_format; + my $default_phone_format; + foreach my $ffmpeg_format( @ffmpeg_formats ) { + if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) { + $default_phone_format = $1; + } elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) { + $default_video_format = $1; + } + } + + if ( $phone && $default_phone_format ) { + $format = $default_phone_format; + } elsif ( $default_video_format ) { + $format = $default_video_format; + } else { + $format = $ffmpeg_formats[0]; + } + + my $command = join( '', + $Config{ZM_PATH_BIN}, + '/zmvideo.pl -e ', + $event->{Id}, + ' -r ', + $rate, + ' -s ', + $scale, + ' -f ', + $format, + ); + my $output = qx($command); + chomp( $output ); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Video generation '$command' failed with status: $status\n" ); + if ( wantarray() ) { + return( undef, undef ); + } + return( 0 ); + } else { + my $sql = 'update Events set Videoed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( wantarray() ) { + return( $format, $output ); + } + } + return( 1 ); +} + +# Returns an image absolute path for given event and frame +# Optionally an analyse image path may be returned if an analyse image exists +# If neither capture nor analyse image exists it will try to extract a frame from .mp4 file if exists +# An empty string is returned if no one from methods above works +sub generateImage { + my $event = shift; + my $frame = shift; + my $analyse = do { @_ ? shift : 0 }; # don't return analyse image by default + + my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', getEventPath($event), $frame->{FrameId}); + my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', getEventPath($event), $frame->{FrameId}) if $analyse; + my $video_path = sprintf('%s/%d-video.mp4', getEventPath($event), $event->{Id}); + my $image_path = ''; + +# check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video + if ( $analyse && -r $analyse_image_path ) { + $image_path = $analyse_image_path; + } elsif ( -r $capture_image_path ) { + $image_path = $capture_image_path; + } elsif ( -e $video_path ) { + if ( !system('ffmpeg -y -v 0 -i '.$video_path." -vf 'select=gte(n\\,".$frame->{FrameId}."),setpts=PTS-STARTPTS' -vframes 1 -f image2 ".$capture_image_path) ) { + $image_path = $capture_image_path; + } + } + return $image_path; +} + + +sub uploadArchFile { + my $filter = shift; + my $event = shift; + + if ( ! $Config{ZM_UPLOAD_HOST} ) { + Error( 'Cannot upload archive as no upload host defined' ); + return( 0 ); + } + + my $archFile = $event->{MonitorName}.'-'.$event->{Id}; + my $archImagePath = getEventPath( $event ) + .'/' + .( + ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) + ? '{*analyse,*capture}' + : '*capture' + ) + .'.jpg' + ; + my @archImageFiles = glob($archImagePath); + my $archLocPath; + + my $archError = 0; + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + $archFile .= '.zip'; + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + my $zip = Archive::Zip->new(); + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + my $status = &AZ_OK; + foreach my $imageFile ( @archImageFiles ) { + Debug( "Adding $imageFile\n" ); + my $member = $zip->addFile( $imageFile ); + if ( !$member ) { + Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); + $archError = 1; + last; + } + $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} + ? &COMPRESSION_DEFLATED + : &COMPRESSION_STORED + ); + } + if ( !$archError ) { + $status = $zip->writeToFileNamed( $archLocPath ); + + if ( $archError = ($status != &AZ_OK) ) { + Error( "Zip error: $status\n " ); + } + } else { + Error( "Error adding images to zip archive $archLocPath, not writing" ); + } + } elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'tar' ) { + if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) { + $archFile .= '.tar.gz'; + } else { + $archFile .= '.tar'; + } + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + if ( $archError = !Archive::Tar->create_archive( + $archLocPath, + $Config{ZM_UPLOAD_ARCH_COMPRESS}, + @archImageFiles + ) + ) { + Error( 'Tar error: '.Archive::Tar->error()."\n " ); + } + } + + if ( $archError ) { + return( 0 ); + } else { + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + Info( 'Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP\n" ); + my $ftp = Net::FTP->new( + $Config{ZM_UPLOAD_HOST}, + Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, + Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, + Debug=>$Config{ZM_UPLOAD_DEBUG} + ); + if ( !$ftp ) { + Error( "Can't create FTP connection: $@" ); + return( 0 ); + } + $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) + or Error( "FTP - Can't login" ); + $ftp->binary() + or Error( "FTP - Can't go binary" ); + $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "FTP - Can't cwd" ) + if ( $Config{ZM_UPLOAD_REM_DIR} ); + $ftp->put( $archLocPath ) + or Error( "FTP - Can't upload '$archLocPath'" ); + $ftp->quit() + or Error( "FTP - Can't quit" ); + } else { + my $host = $Config{ZM_UPLOAD_HOST}; + $host .= ':'.$Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + Info( 'Uploading to '.$host." using SFTP\n" ); + my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); + $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} + if $Config{ZM_UPLOAD_PASS}; + $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} + if $Config{ZM_UPLOAD_TIMEOUT}; + my @more_ssh_args; + push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' + if ! $Config{ZM_UPLOAD_STRICT}; + push @more_ssh_args, '-v' + if $Config{ZM_UPLOAD_DEBUG}; + $sftpOptions{more} = [@more_ssh_args]; + my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); + if ( $sftp->error ) { + Error( "Can't create SFTP connection: ".$sftp->error ); + return( 0 ); + } + $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "SFTP - Can't setcwd: ".$sftp->error ) + if $Config{ZM_UPLOAD_REM_DIR}; + $sftp->put( $archLocPath, $archFile ) + or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); + } + unlink( $archLocPath ); + my $sql = 'update Events set Uploaded = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); +} + +sub substituteTags { + my $text = shift; + my $filter = shift; + my $event = shift; + my $attachments_ref = shift; + +# First we'd better check what we need to get +# We have a filter and an event, do we need any more +# monitor information? + my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + + my $monitor = {}; + if ( $need_monitor ) { + my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); + my $sql = "SELECT + M.Id, + count(E.Id) as EventCount, + count(if(E.Archived,1,NULL)) + as ArchEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) + as HourEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) + as DayEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) + as WeekEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) + as MonthEventCount + FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id + WHERE MonitorId = ? + GROUP BY E.MonitorId + ORDER BY Id" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $monitor = $sth->fetchrow_hashref(); + $sth->finish(); + return() if ( !$monitor ); + } + +# Do we need the image information too? + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; + my $first_alarm_frame; + my $max_alarm_frame; + my $max_alarm_score = 0; + if ( $need_images ) { + my $sql = "SELECT * FROM Frames + WHERE EventId = ? AND Type = 'Alarm' + ORDER BY FrameId" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + while( my $frame = $sth->fetchrow_hashref() ) { + if ( !$first_alarm_frame ) { + $first_alarm_frame = $frame; + } + if ( $frame->{Score} > $max_alarm_score ) { + $max_alarm_frame = $frame; + $max_alarm_score = $frame->{Score}; + } } $sth->finish(); - Debug( "Got " . @filters . " filters" ); - return( \@filters ); + } + + my $url = $Config{ZM_URL}; + $text =~ s/%ZP%/$url/g; + $text =~ s/%MN%/$event->{MonitorName}/g; + $text =~ s/%MET%/$monitor->{EventCount}/g; + $text =~ s/%MEH%/$monitor->{HourEventCount}/g; + $text =~ s/%MED%/$monitor->{DayEventCount}/g; + $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; + $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; + $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; + $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; + $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; + $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; + $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EI%/$event->{Id}/g; + $text =~ s/%EN%/$event->{Name}/g; + $text =~ s/%EC%/$event->{Cause}/g; + $text =~ s/%ED%/$event->{Notes}/g; + $text =~ s/%ET%/$event->{StartTime}/g; + $text =~ s/%EL%/$event->{Length}/g; + $text =~ s/%EF%/$event->{Frames}/g; + $text =~ s/%EFA%/$event->{AlarmFrames}/g; + $text =~ s/%EST%/$event->{TotScore}/g; + $text =~ s/%ESA%/$event->{AvgScore}/g; + $text =~ s/%ESM%/$event->{MaxScore}/g; + + if ( $first_alarm_frame ) { + $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + if ( $attachments_ref && $text =~ s/%EI1%//g ) { + my $path = generateImage( $event, $first_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + + if ( $attachments_ref && $text =~ s/%EIM%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + if ( $attachments_ref && $text =~ s/%EI1A%//g ) { + my $path = generateImage( $event, $first_alarm_frame, 'analyse' ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + if ( $attachments_ref && $text =~ s/%EIMA%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame, 'analyse'); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + } # end if $first_alarm_frame + + if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) { + if ( $text =~ s/%EV%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + if ( $text =~ s/%EVM%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event, 1 ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + } + $text =~ s/%FN%/$filter->{Name}/g; + ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; + $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; + + return( $text ); +} # end subsitituteTags + +sub sendEmail { + my $filter = shift; + my $event = shift; + + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending email" ); + return( 0 ); + } + if ( ! $Config{ZM_EMAIL_ADDRESS} ) { + Error( 'No email address defined, not sending email' ); + return( 0 ); + } + + Info( "Creating notification email\n" ); + + my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification email '$subject'\n" ); + + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); + } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + } + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification email sent\n" ); + } + my $sql = 'update Events set Emailed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } -sub checkFilter -{ - my $filter = shift; +sub sendMessage { + my $filter = shift; + my $event = shift; - Debug( "Checking filter '$filter->{Name}'". - ($filter->{AutoDelete}?", delete":""). - ($filter->{AutoArchive}?", archive":""). - ($filter->{AutoVideo}?", video":""). - ($filter->{AutoUpload}?", upload":""). - ($filter->{AutoEmail}?", email":""). - ($filter->{AutoMessage}?", message":""). - ($filter->{AutoExecute}?", execute":""). - "\n" - ); + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending message" ); + return( 0 ); + } + if ( ! $Config{ZM_MESSAGE_ADDRESS} ) { + Error( 'No message address defined, not sending message' ); + return( 0 ); + } - foreach my $event ( $filter->Execute() ) { - Debug( "Checking event $event->{Id}\n" ); - my $delete_ok = !undef; -$dbh->ping(); - if ( $filter->{AutoArchive} ) - { - Info( "Archiving event $event->{Id}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "update Events set Archived = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); - } - if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) - { - if ( !$event->{Videoed} ) - { - $delete_ok = undef if ( !generateVideo( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) - { - if ( !$event->{Emailed} ) - { - $delete_ok = undef if ( !sendEmail( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) - { - if ( !$event->{Messaged} ) - { - $delete_ok = undef if ( !sendMessage( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) - { - if ( !$event->{Uploaded} ) - { - $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); - } - } - if ( $filter->{AutoExecute} ) - { - if ( !$event->{Execute} ) - { - $delete_ok = undef if ( !executeCommand( $filter, $event ) ); - } - } - if ( $filter->{AutoDelete} ) - { - if ( $delete_ok ) - { - Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "delete from Events where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + Info( "Creating notification message\n" ); - if ( ! $Config{ZM_OPT_FAST_DELETE} ) - { - my $sql = "delete from Frames where EventId = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); - $sql = "delete from Stats where EventId = ?"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + Info( "Sending notification message '$subject'\n" ); - deleteEventFiles( $event->{Id}, $event->{MonitorId} ); - } - } - else - { - Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); - } + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, + MailFrom => $Config{ZM_FROM_EMAIL} + ); } + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification message sent\n" ); + } + my $sql = 'update Events set Messaged = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } -sub generateVideo -{ - my $filter = shift; - my $event = shift; - my $phone = shift; +sub executeCommand { + my $filter = shift; + my $event = shift; - my $rate = $event->{DefaultRate}/100; - my $scale = $event->{DefaultScale}/100; - my $format; + my $event_path = getEventPath( $event ); - my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); - my $default_video_format; - my $default_phone_format; - foreach my $ffmpeg_format( @ffmpeg_formats ) - { - if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) - { - $default_phone_format = $1; - } - elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) - { - $default_video_format = $1; - } - } + my $command = $filter->{AutoExecuteCmd}; + $command .= " $event_path"; + $command = substituteTags( $command, $filter, $event ); - if ( $phone && $default_phone_format ) - { - $format = $default_phone_format; - } - elsif ( $default_video_format ) - { - $format = $default_video_format; - } - else - { - $format = $ffmpeg_formats[0]; - } - - my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e " - .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format; - my $output = qx($command); + Info( "Executing '$command'\n" ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { chomp( $output ); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Video generation '$command' failed with status: $status\n" ); - if ( wantarray() ) - { - return( undef, undef ); - } - return( 0 ); - } - else - { - my $sql = "update Events set Videoed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( wantarray() ) - { - return( $format, $output ); - } - } - return( 1 ); -} - -sub uploadArchFile -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_UPLOAD_HOST} ) - { - Error( "Cannot upload archive as no upload host defined" ); - return( 0 ); - } - - my $archFile = $event->{MonitorName}.'-'.$event->{Id}; - my $archImagePath = getEventPath( $event ) - ."/" - .( - ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) - ? '{*analyse,*capture}' - : '*capture' - ) - .".jpg" - ; - my @archImageFiles = glob($archImagePath); - my $archLocPath; - - my $archError = 0; - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - $archFile .= '.zip'; - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - my $zip = Archive::Zip->new(); - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - - my $status = &AZ_OK; - foreach my $imageFile ( @archImageFiles ) - { - Debug( "Adding $imageFile\n" ); - my $member = $zip->addFile( $imageFile ); - if ( !$member ) - { - Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); - $archError = 1; - last; - } - $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} - ? &COMPRESSION_DEFLATED - : &COMPRESSION_STORED - ); - } - if ( !$archError ) - { - $status = $zip->writeToFileNamed( $archLocPath ); - - if ( $archError = ($status != &AZ_OK) ) - { - Error( "Zip error: $status\n " ); - } - } - else - { - Error( "Error adding images to zip archive $archLocPath, not writing" ); - } - } - elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" ) - { - if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) - { - $archFile .= '.tar.gz'; - } - else - { - $archFile .= '.tar'; - } - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - - if ( $archError = !Archive::Tar->create_archive( - $archLocPath, - $Config{ZM_UPLOAD_ARCH_COMPRESS}, - @archImageFiles - ) - ) - { - Error( "Tar error: ".Archive::Tar->error()."\n " ); - } - } - - if ( $archError ) - { - return( 0 ); - } - else - { - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" ); - my $ftp = Net::FTP->new( - $Config{ZM_UPLOAD_HOST}, - Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, - Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, - Debug=>$Config{ZM_UPLOAD_DEBUG} - ); - if ( !$ftp ) - { - Error( "Can't create FTP connection: $@" ); - return( 0 ); - } - $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) - or Error( "FTP - Can't login" ); - $ftp->binary() - or Error( "FTP - Can't go binary" ); - $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "FTP - Can't cwd" ) - if ( $Config{ZM_UPLOAD_REM_DIR} ); - $ftp->put( $archLocPath ) - or Error( "FTP - Can't upload '$archLocPath'" ); - $ftp->quit() - or Error( "FTP - Can't quit" ); - } - else - { - my $host = $Config{ZM_UPLOAD_HOST}; - $host .= ":".$Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - Info( "Uploading to ".$host." using SFTP\n" ); - my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); - $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} - if $Config{ZM_UPLOAD_PASS}; - $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} - if $Config{ZM_UPLOAD_TIMEOUT}; - my @more_ssh_args; - push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' - if ! $Config{ZM_UPLOAD_STRICT}; - push @more_ssh_args, '-v' - if $Config{ZM_UPLOAD_DEBUG}; - $sftpOptions{more} = [@more_ssh_args]; - my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); - if ( $sftp->error ) - { - Error( "Can't create SFTP connection: ".$sftp->error ); - return( 0 ); - } - $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "SFTP - Can't setcwd: ".$sftp->error ) - if $Config{ZM_UPLOAD_REM_DIR}; - $sftp->put( $archLocPath, $archFile ) - or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); - } - unlink( $archLocPath ); - my $sql = "update Events set Uploaded = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); -} - -sub substituteTags -{ - my $text = shift; - my $filter = shift; - my $event = shift; - my $attachments_ref = shift; - - # First we'd better check what we need to get - # We have a filter and an event, do we need any more - # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; - - my $monitor = {}; - if ( $need_monitor ) - { - my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); - my $sql = "SELECT - M.Id, - count(E.Id) as EventCount, - count(if(E.Archived,1,NULL)) - as ArchEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) - as HourEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) - as DayEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) - as WeekEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) - as MonthEventCount - FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id - WHERE MonitorId = ? - GROUP BY E.MonitorId - ORDER BY Id" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - $monitor = $sth->fetchrow_hashref(); - $sth->finish(); - return() if ( !$monitor ); - } - - # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/; - my $first_alarm_frame; - my $max_alarm_frame; - my $max_alarm_score = 0; - if ( $need_images ) - { - my $sql = "SELECT * FROM Frames - WHERE EventId = ? AND Type = 'Alarm' - ORDER BY FrameId" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - while( my $frame = $sth->fetchrow_hashref() ) - { - if ( !$first_alarm_frame ) - { - $first_alarm_frame = $frame; - } - if ( $frame->{Score} > $max_alarm_score ) - { - $max_alarm_frame = $frame; - $max_alarm_score = $frame->{Score}; - } - } - $sth->finish(); - } - - my $url = $Config{ZM_URL}; - $text =~ s/%ZP%/$url/g; - $text =~ s/%MN%/$event->{MonitorName}/g; - $text =~ s/%MET%/$monitor->{EventCount}/g; - $text =~ s/%MEH%/$monitor->{HourEventCount}/g; - $text =~ s/%MED%/$monitor->{DayEventCount}/g; - $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; - $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; - $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; - $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; - $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; - $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; - $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EI%/$event->{Id}/g; - $text =~ s/%EN%/$event->{Name}/g; - $text =~ s/%EC%/$event->{Cause}/g; - $text =~ s/%ED%/$event->{Notes}/g; - $text =~ s/%ET%/$event->{StartTime}/g; - $text =~ s/%EL%/$event->{Length}/g; - $text =~ s/%EF%/$event->{Frames}/g; - $text =~ s/%EFA%/$event->{AlarmFrames}/g; - $text =~ s/%EST%/$event->{TotScore}/g; - $text =~ s/%ESA%/$event->{AvgScore}/g; - $text =~ s/%ESM%/$event->{MaxScore}/g; - if ( $first_alarm_frame ) - { - $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; - if ( $attachments_ref && $text =~ s/%EI1%//g ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $first_alarm_frame->{FrameId} - ) - } - ); - } - if ( $attachments_ref && $text =~ s/%EIM%//g ) - { - # Don't attach the same image twice - if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) - ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $max_alarm_frame->{FrameId} - ) - } - ); - } - } - } - if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) - { - if ( $text =~ s/%EV%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - if ( $text =~ s/%EVM%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event, 1 ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - } - $text =~ s/%FN%/$filter->{Name}/g; - ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; - $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; - - return( $text ); -} - -sub sendEmail -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending email" ); - return( 0 ); - } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) - { - Error( "No email address defined, not sending email" ); - return( 0 ); - } - - Info( "Creating notification email\n" ); - - my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification email '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification email sent\n" ); - } - my $sql = "update Events set Emailed = 1 where Id = ?"; + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Command '$command' exited with status: $status\n" ); + return( 0 ); + } else { + my $sql = 'update Events set Executed = 1 where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - return( 1 ); + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } - -sub sendMessage -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending message" ); - return( 0 ); - } - if ( ! $Config{ZM_MESSAGE_ADDRESS} ) - { - Error( "No message address defined, not sending message" ); - return( 0 ); - } - - Info( "Creating notification message\n" ); - - my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification message '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, - MailFrom => $Config{ZM_FROM_EMAIL} - ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification message sent\n" ); - } - my $sql = "update Events set Messaged = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - return( 1 ); -} - -sub executeCommand -{ - my $filter = shift; - my $event = shift; - - my $event_path = getEventPath( $event ); - - my $command = $filter->{AutoExecuteCmd}; - $command .= " $event_path"; - - Info( "Executing '$command'\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Command '$command' exited with status: $status\n" ); - return( 0 ); - } - else - { - my $sql = "update Events set Executed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); -} - diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index cd9910aa6..597d7ac1a 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -251,10 +251,6 @@ if ( $command =~ /^(?:start|restart)$/ ) } if ( $monitor->{Function} ne 'Monitor' ) { - if ( $Config{ZM_OPT_FRAME_SERVER} ) - { - runCommand( "zmdc.pl start zmf -m $monitor->{Id}" ); - } runCommand( "zmdc.pl start zma -m $monitor->{Id}" ); } if ( $Config{ZM_OPT_CONTROL} ) diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 3fc5f43e9..0c526c83e 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -196,8 +196,10 @@ foreach my $connection ( @in_select_connections ) my %spawned_connections; my %monitors; - my $monitor_reload_time = 0; +my $needsReload = 0; +loadMonitors(); + $! = undef; my $rin = ''; @@ -313,6 +315,14 @@ while( 1 ) my @out_messages; foreach my $monitor ( values(%monitors) ) { + + if ( ! zmMemVerify($monitor) ) { + # Our attempt to verify the memory handle failed. We should reload the monitors. + # Don't need to zmMemInvalidate because the monitor reload will do it. + $needsReload = 1; + next; + } + my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state", @@ -413,7 +423,7 @@ while( 1 ) } # If necessary reload monitors - if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) + if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )) { foreach my $monitor ( values(%monitors) ) { @@ -421,6 +431,7 @@ while( 1 ) zmMemInvalidate( $monitor ); } loadMonitors(); + $needsReload = 0; } } Info( "Trigger daemon exiting\n" ); @@ -443,7 +454,11 @@ sub loadMonitors or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok + # Check shared memory ok + if ( !zmMemVerify( $monitor ) ) { + zmMemInvalidate( $monitor ); + next; + } if ( defined($monitors{$monitor->{Id}}->{LastState}) ) { diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index cb0781c2e..b263ab065 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database =head1 SYNOPSIS - zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] +zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] =head1 DESCRIPTION @@ -37,15 +37,15 @@ configuring upgrades etc, including on the fly upgrades. =head1 OPTIONS - -c, --check - Check for updated versions of ZoneMinder - -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi - --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. - -v, --version= - Force upgrade to the current version from - -u, --user= - Alternate DB user with privileges to alter DB - -p, --pass= - Password of alternate DB user with privileges to alter DB - -d,--dir= - Directory containing update files if not in default build location - -interactive - interact with the user - -nointeractive - do not interact with the user +-c, --check - Check for updated versions of ZoneMinder +-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi +--migrate-events - Update database structures as per USE_DEEP_STORAGE setting. +-v, --version= - Force upgrade to the current version from +-u, --user= - Alternate DB user with privileges to alter DB +-p, --pass= - Password of alternate DB user with privileges to alter DB +-d,--dir= - Directory containing update files if not in default build location +-interactive - interact with the user +-nointeractive - do not interact with the user =cut use strict; @@ -114,963 +114,858 @@ GetOptions( 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, 'dir:s' =>\$updateDir -) or pod2usage(-exitstatus => -1); + ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(); $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; -if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) -{ - if ( $Config{ZM_DYN_DB_VERSION} ) - { - $version = $Config{ZM_DYN_DB_VERSION}; - } - else - { - print( STDERR "Please give a valid option\n" ); - pod2usage(-exitstatus => -1); - } -} - -if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) -{ - print( STDERR "Please give only one option\n" ); +if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) { + if ( $Config{ZM_DYN_DB_VERSION} ) { + $version = $Config{ZM_DYN_DB_VERSION}; + } else { + print( STDERR "Please give a valid option\n" ); pod2usage(-exitstatus => -1); + } } -if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) -{ - print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); - - my $currVersion = $Config{ZM_DYN_CURR_VERSION}; - my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; - my $lastCheck = $Config{ZM_DYN_LAST_CHECK}; - - if ( !$currVersion ) - { - $currVersion = $Config{ZM_VERSION}; - - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - - while( 1 ) - { - my $now = time(); - if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) - { - Info( "Checking for updates\n" ); - - use LWP::UserAgent; - my $ua = LWP::UserAgent->new; - $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); - if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { - $ua->proxy( "http", $Config{ZM_UPDATE_CHECK_PROXY} ); - } - my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); - my $res = $ua->request($req); - - if ( $res->is_success ) - { - $lastVersion = $res->content; - chomp($lastVersion); - $lastCheck = $now; - - Info( "Got version: '".$lastVersion."'\n" ); - - my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; - my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); - my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); - $lv_sth->finish(); - - my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; - my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); - my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); - $lc_sth->finish(); - } - else - { - Error( "Error check failed: '".$res->status_line()."'\n" ); - } - } - sleep( 3600 ); - } - print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) { + print( STDERR "Please give only one option\n" ); + pod2usage(-exitstatus => -1); } -if ( $rename ) -{ - require File::Find; - chdir( EVENT_PATH ); +if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { + print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); - sub renameImage - { - my $file = $_; + my $currVersion = $Config{ZM_DYN_CURR_VERSION}; + my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; + my $lastCheck = $Config{ZM_DYN_LAST_CHECK}; - # Ignore directories - if ( -d $file ) - { - print( "Checking directory '$file'\n" ); - return; - } - if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) - { - return; - } - my $newFile = "$2-$1$3"; + if ( !$currVersion ) { + $currVersion = $Config{ZM_VERSION}; - print( "Renaming '$file' to '$newFile'\n" ); - rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); - } - - File::Find::find( \&renameImage, '.' ); -} -if ( $zoneFix ) -{ - - my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; + my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @zones; - while( my $zone = $sth->fetchrow_hashref() ) - { - push( @zones, $zone ); - } + my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); + } - $sql = "update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $zone ( @zones ) - { - my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; - my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; - my $zone_area = $zone_width * $zone_height; - my $monitor_area = $zone->{MonitorWidth} * $zone->{MonitorHeight}; - my $res = $sth->execute( - ($zone->{MinAlarmPixels}*$monitor_area)/$zone_area, - ($zone->{MaxAlarmPixels}*$monitor_area)/$zone_area, - ($zone->{MinFilterPixels}*$monitor_area)/$zone_area, - ($zone->{MaxFilterPixels}*$monitor_area)/$zone_area, - ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, - ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, - $zone->{Id} + while( 1 ) { + my $now = time(); + if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { + Info( "Checking for updates\n" ); + + use LWP::UserAgent; + my $ua = LWP::UserAgent->new; + $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); + if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { + $ua->proxy( "http", $Config{ZM_UPDATE_CHECK_PROXY} ); + } + my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); + my $res = $ua->request($req); + + if ( $res->is_success ) { + $lastVersion = $res->content; + chomp($lastVersion); + $lastCheck = $now; + + Info( "Got version: '".$lastVersion."'\n" ); + + my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; + my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); + my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); + $lv_sth->finish(); + + my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; + my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); + my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); + $lc_sth->finish(); + } else { + Error( "Error check failed: '".$res->status_line()."'\n" ); + } + } + sleep( 3600 ); + } + print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +} + +if ( $rename ) { + require File::Find; + + chdir( EVENT_PATH ); + + sub renameImage { + my $file = $_; + +# Ignore directories + if ( -d $file ) { + print( "Checking directory '$file'\n" ); + return; + } + if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) { + return; + } + my $newFile = "$2-$1$3"; + + print( "Renaming '$file' to '$newFile'\n" ); + rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); + } + + File::Find::find( \&renameImage, '.' ); +} +if ( $zoneFix ) { + + my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @zones; + while( my $zone = $sth->fetchrow_hashref() ) { + push( @zones, $zone ); + } + $sth->finish(); + + $sql = "update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $zone ( @zones ) { + my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; + my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; + my $zone_area = $zone_width * $zone_height; + my $monitor_area = $zone->{MonitorWidth} * $zone->{MonitorHeight}; + my $res = $sth->execute( + ($zone->{MinAlarmPixels}*$monitor_area)/$zone_area, + ($zone->{MaxAlarmPixels}*$monitor_area)/$zone_area, + ($zone->{MinFilterPixels}*$monitor_area)/$zone_area, + ($zone->{MaxFilterPixels}*$monitor_area)/$zone_area, + ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, + ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, + $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); +} +if ( $migrateEvents ) { + my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; + my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; + + if ( !(($> == 0) || ($> == $webUid)) ) { + print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); + exit( -1 ); + } + +# Run as web user/group + $( = $webGid; + $) = $webGid; + $< = $webUid; + $> = $webUid; + + print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); + my $response = ; + chomp( $response ); + while ( $response !~ /^[yYnN]$/ ) { + print( "Please press 'y' to continue or 'n' to abort only : " ); + $response = ; + chomp( $response ); + } + + if ( $response =~ /^[yY]$/ ) { + print( "Converting all events to deep storage.\n" ); + + chdir( $Config{ZM_PATH_WEB} ); + my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute(); + if ( !$res ) { + Fatal( "Can't fetch Events: ".$sth->errstr() ); + } + + while( my $event = $sth->fetchrow_hashref() ) { + my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; + + if ( !-d $oldEventPath ) { + print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); + next; + } + + print( "Converting event ".$event->{Id}."\n" ); + my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); + my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); + my $newEventPath = $newDatePath.'/'.$newTimePath; + ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; + makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); + my $idLink = $newDatePath.'/.'.$event->{Id}; + symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); + rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); } $sth->finish(); + + print( "Updating configuration.\n" ); + $sql = "update Config set Value = ? where Name = 'ZM_USE_DEEP_STORAGE'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + + print( "All events converted.\n\n" ); + } else { + print( "Aborting event conversion.\n\n" ); + } } -if ( $migrateEvents ) -{ - my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; - my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; - - if ( !(($> == 0) || ($> == $webUid)) ) - { - print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); - exit( -1 ); - } - - # Run as web user/group - $( = $webGid; - $) = $webGid; - $< = $webUid; - $> = $webUid; - - print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); - my $response = ; - chomp( $response ); - while ( $response !~ /^[yYnN]$/ ) - { - print( "Please press 'y' to continue or 'n' to abort only : " ); - $response = ; - chomp( $response ); - } - - if ( $response =~ /^[yY]$/ ) - { - print( "Converting all events to deep storage.\n" ); - - chdir( $Config{ZM_PATH_WEB} ); - my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; - my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute(); - if ( !$res ) - { - Fatal( "Can't fetch Events: ".$sth->errstr() ); - } - - while( my $event = $sth->fetchrow_hashref() ) - { - my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; - - if ( !-d $oldEventPath ) - { - print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); - next; - } - - print( "Converting event ".$event->{Id}."\n" ); - my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); - my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); - my $newEventPath = $newDatePath.'/'.$newTimePath; - ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; - makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); - my $idLink = $newDatePath.'/.'.$event->{Id}; - symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); - rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); - } - $sth->finish(); - - print( "Updating configuration.\n" ); - $sql = "update Config set Value = ? where Name = 'ZM_USE_DEEP_STORAGE'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - - print( "All events converted.\n\n" ); - } - else - { - print( "Aborting event conversion.\n\n" ); - } -} -if ( $freshen ) -{ - print( "\nFreshening configuration in database\n" ); - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); +if ( $freshen ) { + print( "\nFreshening configuration in database\n" ); + ZoneMinder::Config::loadConfigFromDB(); + ZoneMinder::Config::saveConfigToDB(); } # Don't do innoDB upgrade if not interactive if ( $interactive ) { - # Now check for MyISAM Tables - my @MyISAM_Tables; - my $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='zm' AND engine = 'MyISAM'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); +# Now check for MyISAM Tables + my @MyISAM_Tables; + my $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='zm' AND engine = 'MyISAM'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $dbTable = $sth->fetchrow() ) { - push @MyISAM_Tables, $dbTable; - } - $sth->finish(); + while( my $dbTable = $sth->fetchrow() ) { + push @MyISAM_Tables, $dbTable; + } + $sth->finish(); - if ( @MyISAM_Tables ) { - print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); - print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); - my $response = ; - chomp( $response ); - if ( $response =~ /^[yY]$/ ) { - $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors - print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; - foreach (@MyISAM_Tables) { + if ( @MyISAM_Tables ) { + print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); + print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); + my $response = ; + chomp( $response ); + if ( $response =~ /^[yY]$/ ) { + $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors + print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; + foreach (@MyISAM_Tables) { my $sql = "ALTER TABLE $_ ENGINE = InnoDB"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - $dbh->do(q|SET sql_mode=''|); # Set mode back to default - } - } + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } + $dbh->do(q|SET sql_mode=''|); # Set mode back to default + } + } } # end if interactive -if ( $version ) -{ - my ( $detaint_version ) = $version =~ /^([\w.]+)$/; - $version = $detaint_version; +if ( $version ) { + my ( $detaint_version ) = $version =~ /^([\w.]+)$/; + $version = $detaint_version; - if ( ZM_VERSION eq $version ) - { - print( "\nDatabase already at version $version, update aborted.\n\n" ); - exit( 0 ); + if ( ZM_VERSION eq $version ) { + print( "\nDatabase already at version $version, update aborted.\n\n" ); + exit( 0 ); + } + + print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); + if ( $interactive ) { + if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) { + print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); + my $response = ; } - print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); - if ( $interactive ) - { - if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) - { - print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); - my $response = ; - } + print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); + my $response = ; - print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); - my $response = ; - - print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); - $response = ; - chomp( $response ); - while ( $response !~ /^[yYnN]$/ ) - { - print( "Please press 'y' for a backup or 'n' to continue only : " ); - $response = ; - chomp( $response ); - } - - if ( $response =~ /^[yY]$/ ) - { - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - my $command = "mysqldump"; - $command .= " -h".$host if defined($host); - $command .= " -P".$port if defined($port); - if ( $dbUser ) - { - $command .= ' -u'.$dbUser; - $command .= ' -p"'.$dbPass.'"' if $dbPass; - } - my $backup = "@ZM_TMPDIR@/".$Config{ZM_DB_NAME}."-".$version.".dump"; - $command .= " --add-drop-table --databases ".$Config{ZM_DB_NAME}." > ".$backup; - print( "Creating backup to $backup. This may take several minutes.\n" ); - print( "Executing '$command'\n" ) if ( logDebugging() ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - print( "Output: $output\n" ); - } - if ( $status ) - { - die( "Command '$command' exited with status: $status\n" ); - } - else - { - print( "Database successfully backed up to $backup, proceeding to upgrade.\n" ); - } - } - elsif ( $response !~ /^[nN]$/ ) - { - die( "Unexpected response '$response'" ); - } - } - sub patchDB - { - my $dbh = shift; - my $version = shift; - - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - my $command = "mysql -h".$host; - $command .= " -P".$port if defined($port); - if ( $dbUser ) - { - $command .= " -u".$dbUser; - $command .= ' -p"'.$dbPass.'"' if $dbPass; - } - $command .= " ".$Config{ZM_DB_NAME}." < "; - if ( $updateDir ) - { - $command .= $updateDir; - } - else - { - $command .= $Config{ZM_PATH_DATA}."/db"; - } - $command .= "/zm_update-".$version.".sql"; - - print( "Executing '$command'\n" ) if ( logDebugging() ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - print( "Output: $output\n" ); - } - if ( $status ) - { - die( "Command '$command' exited with status: $status\n" ); - } - else - { - print( "\nDatabase successfully upgraded to version $version.\n" ); - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); - } + print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); + $response = ; + chomp( $response ); + while ( $response !~ /^[yYnN]$/ ) { + print( "Please press 'y' for a backup or 'n' to continue only : " ); + $response = ; + chomp( $response ); } - - print( "\nUpgrading database to version ".ZM_VERSION."\n" ); - - # Update config first of all - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); - - my $cascade = undef; - if ( $cascade || $version eq "1.19.0" ) - { - # Patch the database - patchDB( $dbh, "1.19.0" ); - $cascade = !undef; + if ( $response =~ /^[yY]$/ ) { + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $command = "mysqldump"; + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $command .= " -S".$portOrSocket; + } else { + $command .= " -h".$host." -P".$portOrSocket; + } + } else { + $command .= " -h".$host; + } + if ( $dbUser ) { + $command .= ' -u'.$dbUser; + $command .= ' -p"'.$dbPass.'"' if $dbPass; + } + my $backup = "@ZM_TMPDIR@/".$Config{ZM_DB_NAME}."-".$version.".dump"; + $command .= " --add-drop-table --databases ".$Config{ZM_DB_NAME}." > ".$backup; + print( "Creating backup to $backup. This may take several minutes.\n" ); + print( "Executing '$command'\n" ) if ( logDebugging() ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp( $output ); + print( "Output: $output\n" ); + } + if ( $status ) { + die( "Command '$command' exited with status: $status\n" ); + } else { + print( "Database successfully backed up to $backup, proceeding to upgrade.\n" ); + } + } elsif ( $response !~ /^[nN]$/ ) { + die( "Unexpected response '$response'" ); } - if ( $cascade || $version eq "1.19.1" ) - { - # Patch the database - patchDB( $dbh, "1.19.1"); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.2" ) - { - # Patch the database - patchDB( $dbh, "1.19.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.3" ) - { - # Patch the database - patchDB( $dbh, "1.19.3" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.4" ) - { - # Rename the event directories and create a new symlink for the names - chdir( EVENT_PATH ); + } + sub patchDB { + my $dbh = shift; + my $version = shift; - my $sql = "select * from Monitors order by Id"; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $command = "mysql"; + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $command .= " -S".$portOrSocket; + } else { + $command .= " -h".$host." -P".$portOrSocket; + } + } else { + $command .= " -h".$host; + } + if ( $dbUser ) { + $command .= " -u".$dbUser; + $command .= ' -p"'.$dbPass.'"' if $dbPass; + } + $command .= " ".$Config{ZM_DB_NAME}." < "; + if ( $updateDir ) { + $command .= $updateDir; + } else { + $command .= $Config{ZM_PATH_DATA}."/db"; + } + $command .= "/zm_update-".$version.".sql"; + + print( "Executing '$command'\n" ) if ( logDebugging() ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp( $output ); + print( "Output: $output\n" ); + } + if ( $status ) { + die( "Command '$command' exited with status: $status\n" ); + } else { + print( "\nDatabase successfully upgraded to version $version.\n" ); + my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); + } + } + + + print( "\nUpgrading database to version ".ZM_VERSION."\n" ); + +# Update config first of all + ZoneMinder::Config::loadConfigFromDB(); + ZoneMinder::Config::saveConfigToDB(); + + my $cascade = undef; + if ( $cascade || $version eq "1.19.0" ) { +# Patch the database + patchDB( $dbh, "1.19.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.1" ) { +# Patch the database + patchDB( $dbh, "1.19.1"); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.2" ) { +# Patch the database + patchDB( $dbh, "1.19.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.3" ) { +# Patch the database + patchDB( $dbh, "1.19.3" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.4" ) { +# Rename the event directories and create a new symlink for the names + chdir( EVENT_PATH ); + + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + while( my $monitor = $sth->fetchrow_hashref() ) { + if ( -d $monitor->{Name} ) { + rename( $monitor->{Name}, $monitor->{Id} ) or warn( "Can't rename existing monitor directory '$monitor->{Name}' to '$monitor->{Id}': $!" ); + symlink( $monitor->{Id}, $monitor->{Name} ) or warn( "Can't symlink monitor directory '$monitor->{Id}' to '$monitor->{Name}': $!" ); + } + } + $sth->finish(); + +# Patch the database + patchDB( $dbh, "1.19.4" ); + + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.5" ) { + print( "\nThis version now only uses one database user.\nPlease ensure you have run zmconfig.pl and re-entered your database username and password prior to upgrading, or the upgrade will fail.\nPress enter to continue or ctrl-C to stop : " ); +# Patch the database + my $dummy = ; + patchDB( $dbh, "1.19.5" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.20.0" ) { +# Patch the database + patchDB( $dbh, "1.20.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.20.1" ) { +# Patch the database + patchDB( $dbh, "1.20.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.0" ) { +# Patch the database + patchDB( $dbh, "1.21.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.1" ) { +# Patch the database + patchDB( $dbh, "1.21.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.2" ) { +# Patch the database + patchDB( $dbh, "1.21.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.3" ) { +# Patch the database + patchDB( $dbh, "1.21.3" ); + +# Add appropriate widths and heights to events + { + print( "Updating events. This may take a few minutes. Please wait.\n" ); + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + while( my $monitor = $sth->fetchrow_hashref() ) { + my $sql = "update Events set Width = ?, Height = ? where MonitorId = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $monitor->{Width}, $monitor->{Height}, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); + } + +# Add sequence numbers + { + print( "Updating monitor sequences. Please wait.\n" ); + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $sequence = 1; + while( my $monitor = $sth->fetchrow_hashref() ) { + my $sql = "update Monitors set Sequence = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $sequence++, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); + } + +# Update saved filters + { + print( "Updating saved filters. Please wait.\n" ); + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @filters; + while( my $filter = $sth->fetchrow_hashref() ) { + push( @filters, $filter ); + } + $sth->finish(); + $sql = "update Filters set Query = ? where Name = ?"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $filter ( @filters ) { + if ( $filter->{Query} =~ /op\d=&/ ) { + ( my $newQuery = $filter->{Query} ) =~ s/(op\d=)&/$1=&/g; + $res = $sth->execute( $newQuery, $filter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + } + + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.4" ) { +# Patch the database + patchDB( $dbh, "1.21.4" ); + +# Convert zones to new format + { + print( "Updating zones. Please wait.\n" ); + +# Get the existing zones from the DB + my $sql = "select Z.*,M.Width,M.Height from Zones as Z inner join Monitors as M on (Z.MonitorId = M.Id)"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @zones; + while( my $zone = $sth->fetchrow_hashref() ) { + push( @zones, $zone ); + } + $sth->finish(); + + no strict 'refs'; + foreach my $zone ( @zones ) { +# Create the coordinate strings + if ( $zone->{Units} eq "Pixels" ) { + my $sql = "update Zones set NumCoords = 4, Coords = concat( LoX,',',LoY,' ',HiX,',',LoY,' ',HiX,',',HiY,' ',LoX,',',HiY ), Area = round( ((HiX-LoX)+1)*((HiY-LoY)+1) ) where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } else { + my $loX = ($zone->{LoX} * ($zone->{Width}-1) ) / 100; + my $hiX = ($zone->{HiX} * ($zone->{Width}-1) ) / 100; + my $loY = ($zone->{LoY} * ($zone->{Height}-1) ) / 100; + my $hiY = ($zone->{HiY} * ($zone->{Height}-1) ) / 100; + my $area = (($hiX-$loX)+1)*(($hiY-$loY)+1); + my $sql = "update Zones set NumCoords = 4, Coords = concat( round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?) ), Area = round(?), MinAlarmPixels = round(?), MaxAlarmPixels = round(?), MinFilterPixels = round(?), MaxFilterPixels = round(?), MinBlobPixels = round(?), MaxBlobPixels = round(?) where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $loX, $loY, $hiX, $loY, $hiX, $hiY, $loX, $hiY, $area, ($zone->{MinAlarmPixels}*$area)/100, ($zone->{MaxAlarmPixels}*$area)/100, ($zone->{MinFilterPixels}*$area)/100, ($zone->{MaxFilterPixels}*$area)/100, ($zone->{MinBlobPixels}*$area)/100, ($zone->{MaxBlobPixels}*$area)/100, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + } +# Convert run states to new format + { + print( "Updating run states. Please wait.\n" ); + +# Get the existing zones from the DB + my $sql = "select * from States"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @states; + while( my $state = $sth->fetchrow_hashref() ) { + push( @states, $state ); + } + $sth->finish(); + + foreach my $state ( @states ) { + my @new_defns; + foreach my $defn ( split( /,/, $state->{Definition} ) ) { + push( @new_defns, $defn.":1" ); + } + my $sql = "update States set Definition = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( join( ',', @new_defns ), $state->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.0" ) { +# Patch the database + patchDB( $dbh, "1.22.0" ); + +# Check for maximum FPS setting and update alarm max fps settings + { + print( "Updating monitors. Please wait.\n" ); + if ( defined(&ZM_NO_MAX_FPS_ON_ALARM) && &ZM_NO_MAX_FPS_ON_ALARM ) { +# Update the individual monitor settings to match the previous global one + my $sql = "update Monitors set AlarmMaxFPS = NULL"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { - if ( -d $monitor->{Name} ) - { - rename( $monitor->{Name}, $monitor->{Id} ) or warn( "Can't rename existing monitor directory '$monitor->{Name}' to '$monitor->{Id}': $!" ); - symlink( $monitor->{Id}, $monitor->{Name} ) or warn( "Can't symlink monitor directory '$monitor->{Id}' to '$monitor->{Name}': $!" ); - } - } - $sth->finish(); - - # Patch the database - patchDB( $dbh, "1.19.4" ); - - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.5" ) - { - print( "\nThis version now only uses one database user.\nPlease ensure you have run zmconfig.pl and re-entered your database username and password prior to upgrading, or the upgrade will fail.\nPress enter to continue or ctrl-C to stop : " ); - # Patch the database - my $dummy = ; - patchDB( $dbh, "1.19.5" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.20.0" ) - { - # Patch the database - patchDB( $dbh, "1.20.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.20.1" ) - { - # Patch the database - patchDB( $dbh, "1.20.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.0" ) - { - # Patch the database - patchDB( $dbh, "1.21.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.1" ) - { - # Patch the database - patchDB( $dbh, "1.21.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.2" ) - { - # Patch the database - patchDB( $dbh, "1.21.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.3" ) - { - # Patch the database - patchDB( $dbh, "1.21.3" ); - - # Add appropriate widths and heights to events - { - print( "Updating events. This may take a few minutes. Please wait.\n" ); - my $sql = "select * from Monitors order by Id"; + } else { +# Update the individual monitor settings to match the previous global one + my $sql = "update Monitors set AlarmMaxFPS = MaxFPS"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { - my $sql = "update Events set Width = ?, Height = ? where MonitorId = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $monitor->{Width}, $monitor->{Height}, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + { + print( "Updating mail configuration. Please wait.\n" ); + my ( $sql, $sth, $res ); + if ( defined(&ZM_EMAIL_TEXT) && &ZM_EMAIL_TEXT ) { + my ( $email_subject, $email_body ) = $Config{ZM_EMAIL_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; + $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_SUBJECT', Value = '".$email_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the email used to send matching event details', Help = 'This option is used to define the subject of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_BODY', Value = '".$email_body."', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the email used to send matching event details', Help = 'This option is used to define the content of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + } + if ( defined(&ZM_MESSAGE_TEXT) && &ZM_MESSAGE_TEXT ) { + my ( $message_subject, $message_body ) = $Config{ZM_MESSAGE_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; + $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_SUBJECT', Value = '".$message_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI%', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the message used to send matching event details', Help = 'This option is used to define the subject of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_BODY', Value = '".$message_body."', Type = 'text', DefaultValue = 'ZM alarm detected - %ED% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the message used to send matching event details', Help = 'This option is used to define the content of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + } + } + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.1" ) { +# Patch the database + patchDB( $dbh, "1.22.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.2" ) { +# Patch the database + patchDB( $dbh, "1.22.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.3" ) { +# Patch the database + patchDB( $dbh, "1.22.3" ); + +# Convert timestamp strings to new format + { + my $sql = "select * from Monitors"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @db_monitors; + while( my $db_monitor = $sth->fetchrow_hashref() ) { + push( @db_monitors, $db_monitor ); + } + $sth->finish(); + foreach my $db_monitor ( @db_monitors ) { + if ( $db_monitor->{LabelFormat} =~ /\%\%s/ ) { + $db_monitor->{LabelFormat} =~ s/\%\%s/%N/; + $db_monitor->{LabelFormat} =~ s/\%\%s/%Q/; + + my $sql = "update Monitors set LabelFormat = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $db_monitor->{LabelFormat}, $db_monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } - $sth->finish(); + } + } + +# Convert filters to new format + { + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @dbFilters; + while( my $dbFilter = $sth->fetchrow_hashref() ) { + push( @dbFilters, $dbFilter ); + } + $sth->finish(); + foreach my $dbFilter ( @dbFilters ) { + my %filter_terms; + foreach my $filter_parm ( split( /&/, $dbFilter->{Query} ) ) { + my( $key, $value ) = split( /=/, $filter_parm, 2 ); + if ( $key ) { + $filter_terms{$key} = $value; + } } + my $filter = { 'terms' => [] }; + for ( my $i = 1; $i <= $filter_terms{trms}; $i++ ) { + my $term = {}; + my $conjunction_name = "cnj$i"; + my $obracket_name = "obr$i"; + my $cbracket_name = "cbr$i"; + my $attr_name = "attr$i"; + my $op_name = "op$i"; + my $value_name = "val$i"; - # Add sequence numbers - { - print( "Updating monitor sequences. Please wait.\n" ); - my $sql = "select * from Monitors order by Id"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my $sequence = 1; - while( my $monitor = $sth->fetchrow_hashref() ) - { - my $sql = "update Monitors set Sequence = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $sequence++, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - $sth->finish(); + $term->{cnj} = $filter_terms{$conjunction_name} if ( $filter_terms{$conjunction_name} ); + $term->{obr} = $filter_terms{$obracket_name} if ( $filter_terms{$obracket_name} ); + $term->{attr} = $filter_terms{$attr_name} if ( $filter_terms{$attr_name} ); + $term->{val} = $filter_terms{$value_name} if ( defined($filter_terms{$value_name}) ); + $term->{op} = $filter_terms{$op_name} if ( $filter_terms{$op_name} ); + $term->{cbr} = $filter_terms{$cbracket_name} if ( $filter_terms{$cbracket_name} ); + push( @{$filter->{terms}}, $term ); } + $filter->{sort_field} = $filter_terms{sort_field} if ( $filter_terms{sort_field} ); + $filter->{sort_asc} = $filter_terms{sort_asc} if ( $filter_terms{sort_asc} ); + $filter->{limit} = $filter_terms{limit} if ( $filter_terms{limit} ); - # Update saved filters - { - print( "Updating saved filters. Please wait.\n" ); - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @filters; - while( my $filter = $sth->fetchrow_hashref() ) - { - push( @filters, $filter ); - } - $sth->finish(); - $sql = "update Filters set Query = ? where Name = ?"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $filter ( @filters ) - { - if ( $filter->{Query} =~ /op\d=&/ ) - { - ( my $newQuery = $filter->{Query} ) =~ s/(op\d=)&/$1=&/g; - $res = $sth->execute( $newQuery, $filter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } + my $newQuery = 'a:'.int(keys(%$filter)).':{s:5:"terms";a:'.int(@{$filter->{terms}}).':{'; + my $i = 0; + foreach my $term ( @{$filter->{terms}} ) { + $newQuery .= 'i:'.$i.';a:'.int(keys(%$term)).':{'; + while ( my ( $key, $val ) = each( %$term ) ) { + $newQuery .= 's:'.length($key).':"'.$key.'";'; + $newQuery .= 's:'.length($val).':"'.$val.'";'; + } + $newQuery .= '}'; + $i++; } - - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.4" ) - { - # Patch the database - patchDB( $dbh, "1.21.4" ); - - # Convert zones to new format - { - print( "Updating zones. Please wait.\n" ); - - # Get the existing zones from the DB - my $sql = "select Z.*,M.Width,M.Height from Zones as Z inner join Monitors as M on (Z.MonitorId = M.Id)"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @zones; - while( my $zone = $sth->fetchrow_hashref() ) - { - push( @zones, $zone ); - } - $sth->finish(); - - no strict 'refs'; - foreach my $zone ( @zones ) - { - # Create the coordinate strings - if ( $zone->{Units} eq "Pixels" ) - { - my $sql = "update Zones set NumCoords = 4, Coords = concat( LoX,',',LoY,' ',HiX,',',LoY,' ',HiX,',',HiY,' ',LoX,',',HiY ), Area = round( ((HiX-LoX)+1)*((HiY-LoY)+1) ) where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - else - { - my $loX = ($zone->{LoX} * ($zone->{Width}-1) ) / 100; - my $hiX = ($zone->{HiX} * ($zone->{Width}-1) ) / 100; - my $loY = ($zone->{LoY} * ($zone->{Height}-1) ) / 100; - my $hiY = ($zone->{HiY} * ($zone->{Height}-1) ) / 100; - my $area = (($hiX-$loX)+1)*(($hiY-$loY)+1); - my $sql = "update Zones set NumCoords = 4, Coords = concat( round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?) ), Area = round(?), MinAlarmPixels = round(?), MaxAlarmPixels = round(?), MinFilterPixels = round(?), MaxFilterPixels = round(?), MinBlobPixels = round(?), MaxBlobPixels = round(?) where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $loX, $loY, $hiX, $loY, $hiX, $hiY, $loX, $hiY, $area, ($zone->{MinAlarmPixels}*$area)/100, ($zone->{MaxAlarmPixels}*$area)/100, ($zone->{MinFilterPixels}*$area)/100, ($zone->{MaxFilterPixels}*$area)/100, ($zone->{MinBlobPixels}*$area)/100, ($zone->{MaxBlobPixels}*$area)/100, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - } + $newQuery .= '}'; + foreach my $field ( "sort_field", "sort_asc", "limit" ) { + if ( defined($filter->{$field}) ) { + $newQuery .= 's:'.length($field).':"'.$field.'";'; + $newQuery .= 's:'.length($filter->{$field}).':"'.$filter->{$field}.'";'; + } } - # Convert run states to new format - { - print( "Updating run states. Please wait.\n" ); + $newQuery .= '}'; - # Get the existing zones from the DB - my $sql = "select * from States"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @states; - while( my $state = $sth->fetchrow_hashref() ) - { - push( @states, $state ); - } - $sth->finish(); + my $sql = "update Filters set Query = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $newQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } - foreach my $state ( @states ) - { - my @new_defns; - foreach my $defn ( split( /,/, $state->{Definition} ) ) - { - push( @new_defns, $defn.":1" ); - } - my $sql = "update States set Definition = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( join( ',', @new_defns ), $state->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } +# Update the stream quality setting to the old image quality ones + { + my $dbh = zmDbConnect(); - $cascade = !undef; + my $sql = "update Config set Value = ? where Name = 'ZM_JPEG_STREAM_QUALITY'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $Config{ZM_JPEG_IMAGE_QUALITY} ) or die( "Can't execute: ".$sth->errstr() ); } - if ( $cascade || $version eq "1.22.0" ) - { - # Patch the database - patchDB( $dbh, "1.22.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.0" ) { +# Patch the database + patchDB( $dbh, "1.23.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.1" ) { +# Patch the database + patchDB( $dbh, "1.23.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.2" ) { +# Patch the database + patchDB( $dbh, "1.23.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.3" ) { +# Patch the database + patchDB( $dbh, "1.23.3" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.0" ) { +# Patch the database + patchDB( $dbh, "1.24.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.1" ) { +# Patch the database + patchDB( $dbh, "1.24.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.2" ) { +# Patch the database + patchDB( $dbh, "1.24.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.3" ) { + my $result = eval { + require PHP::Serialization; + PHP::Serialization->import(); + }; + die( "Unable to perform upgrade from 1.24.3, PHP::Serialization module not found" ) if ( $result ); - # Check for maximum FPS setting and update alarm max fps settings - { - print( "Updating monitors. Please wait.\n" ); - if ( defined(&ZM_NO_MAX_FPS_ON_ALARM) && &ZM_NO_MAX_FPS_ON_ALARM ) - { - # Update the individual monitor settings to match the previous global one - my $sql = "update Monitors set AlarmMaxFPS = NULL"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - else - { - # Update the individual monitor settings to match the previous global one - my $sql = "update Monitors set AlarmMaxFPS = MaxFPS"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - } - { - print( "Updating mail configuration. Please wait.\n" ); - my ( $sql, $sth, $res ); - if ( defined(&ZM_EMAIL_TEXT) && &ZM_EMAIL_TEXT ) - { - my ( $email_subject, $email_body ) = $Config{ZM_EMAIL_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; - $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_SUBJECT', Value = '".$email_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the email used to send matching event details', Help = 'This option is used to define the subject of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_BODY', Value = '".$email_body."', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the email used to send matching event details', Help = 'This option is used to define the content of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - if ( defined(&ZM_MESSAGE_TEXT) && &ZM_MESSAGE_TEXT ) - { - my ( $message_subject, $message_body ) = $Config{ZM_MESSAGE_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; - $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_SUBJECT', Value = '".$message_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI%', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the message used to send matching event details', Help = 'This option is used to define the subject of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_BODY', Value = '".$message_body."', Type = 'text', DefaultValue = 'ZM alarm detected - %ED% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the message used to send matching event details', Help = 'This option is used to define the content of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - } - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.1" ) - { - # Patch the database - patchDB( $dbh, "1.22.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.2" ) - { - # Patch the database - patchDB( $dbh, "1.22.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.3" ) - { - # Patch the database - patchDB( $dbh, "1.22.3" ); +# Patch the database + patchDB( $dbh, "1.24.3" ); - # Convert timestamp strings to new format - { - my $sql = "select * from Monitors"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @db_monitors; - while( my $db_monitor = $sth->fetchrow_hashref() ) - { - push( @db_monitors, $db_monitor ); - } - $sth->finish(); - foreach my $db_monitor ( @db_monitors ) - { - if ( $db_monitor->{LabelFormat} =~ /\%\%s/ ) - { - $db_monitor->{LabelFormat} =~ s/\%\%s/%N/; - $db_monitor->{LabelFormat} =~ s/\%\%s/%Q/; - - my $sql = "update Monitors set LabelFormat = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $db_monitor->{LabelFormat}, $db_monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - } - } - - # Convert filters to new format - { - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @dbFilters; - while( my $dbFilter = $sth->fetchrow_hashref() ) - { - push( @dbFilters, $dbFilter ); - } - $sth->finish(); - foreach my $dbFilter ( @dbFilters ) - { - my %filter_terms; - foreach my $filter_parm ( split( /&/, $dbFilter->{Query} ) ) - { - my( $key, $value ) = split( /=/, $filter_parm, 2 ); - if ( $key ) - { - $filter_terms{$key} = $value; - } - } - my $filter = { 'terms' => [] }; - for ( my $i = 1; $i <= $filter_terms{trms}; $i++ ) - { - my $term = {}; - my $conjunction_name = "cnj$i"; - my $obracket_name = "obr$i"; - my $cbracket_name = "cbr$i"; - my $attr_name = "attr$i"; - my $op_name = "op$i"; - my $value_name = "val$i"; - - $term->{cnj} = $filter_terms{$conjunction_name} if ( $filter_terms{$conjunction_name} ); - $term->{obr} = $filter_terms{$obracket_name} if ( $filter_terms{$obracket_name} ); - $term->{attr} = $filter_terms{$attr_name} if ( $filter_terms{$attr_name} ); - $term->{val} = $filter_terms{$value_name} if ( defined($filter_terms{$value_name}) ); - $term->{op} = $filter_terms{$op_name} if ( $filter_terms{$op_name} ); - $term->{cbr} = $filter_terms{$cbracket_name} if ( $filter_terms{$cbracket_name} ); - push( @{$filter->{terms}}, $term ); - } - $filter->{sort_field} = $filter_terms{sort_field} if ( $filter_terms{sort_field} ); - $filter->{sort_asc} = $filter_terms{sort_asc} if ( $filter_terms{sort_asc} ); - $filter->{limit} = $filter_terms{limit} if ( $filter_terms{limit} ); - - my $newQuery = 'a:'.int(keys(%$filter)).':{s:5:"terms";a:'.int(@{$filter->{terms}}).':{'; - my $i = 0; - foreach my $term ( @{$filter->{terms}} ) - { - $newQuery .= 'i:'.$i.';a:'.int(keys(%$term)).':{'; - while ( my ( $key, $val ) = each( %$term ) ) - { - $newQuery .= 's:'.length($key).':"'.$key.'";'; - $newQuery .= 's:'.length($val).':"'.$val.'";'; - } - $newQuery .= '}'; - $i++; - } - $newQuery .= '}'; - foreach my $field ( "sort_field", "sort_asc", "limit" ) - { - if ( defined($filter->{$field}) ) - { - $newQuery .= 's:'.length($field).':"'.$field.'";'; - $newQuery .= 's:'.length($filter->{$field}).':"'.$filter->{$field}.'";'; - } - } - $newQuery .= '}'; - - my $sql = "update Filters set Query = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $newQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } - - # Update the stream quality setting to the old image quality ones - { - my $dbh = zmDbConnect(); - - my $sql = "update Config set Value = ? where Name = 'ZM_JPEG_STREAM_QUALITY'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_JPEG_IMAGE_QUALITY} ) or die( "Can't execute: ".$sth->errstr() ); - } - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.0" ) +# Convert filters to JSON from PHP format serialisation { - # Patch the database - patchDB( $dbh, "1.23.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.1" ) - { - # Patch the database - patchDB( $dbh, "1.23.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.2" ) - { - # Patch the database - patchDB( $dbh, "1.23.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.3" ) - { - # Patch the database - patchDB( $dbh, "1.23.3" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.0" ) - { - # Patch the database - patchDB( $dbh, "1.24.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.1" ) - { - # Patch the database - patchDB( $dbh, "1.24.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.2" ) - { - # Patch the database - patchDB( $dbh, "1.24.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.3" ) - { - my $result = eval - { - require PHP::Serialization; - PHP::Serialization->import(); + print( "\nConverting filters from PHP to JSON format\n" ); + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @dbFilters; + while( my $dbFilter = $sth->fetchrow_hashref() ) { + push( @dbFilters, $dbFilter ); + } + $sth->finish(); + foreach my $dbFilter ( @dbFilters ) { + print( " ".$dbFilter->{Name} ); + eval { + my $phpQuery = $dbFilter->{Query}; + my $query = PHP::Serialization::unserialize( $phpQuery ); + my $jsonQuery = jsonEncode( $query ); + my $sql = "update Filters set Query = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $jsonQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); }; - die( "Unable to perform upgrade from 1.24.3, PHP::Serialization module not found" ) if ( $result ); - - # Patch the database - patchDB( $dbh, "1.24.3" ); - - # Convert filters to JSON from PHP format serialisation - { - print( "\nConverting filters from PHP to JSON format\n" ); - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @dbFilters; - while( my $dbFilter = $sth->fetchrow_hashref() ) - { - push( @dbFilters, $dbFilter ); - } - $sth->finish(); - foreach my $dbFilter ( @dbFilters ) - { - print( " ".$dbFilter->{Name} ); - eval { - my $phpQuery = $dbFilter->{Query}; - my $query = PHP::Serialization::unserialize( $phpQuery ); - my $jsonQuery = jsonEncode( $query ); - my $sql = "update Filters set Query = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $jsonQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - }; - if ( $@ ) - { - print( " - failed, please check or report. Query is '".$dbFilter->{Query}."'\n" ); - print( $@ ); - } - else - { - print( " - complete\n" ); - } - } - print( "Conversion complete\n" ); + if ( $@ ) { + print( " - failed, please check or report. Query is '".$dbFilter->{Query}."'\n" ); + print( $@ ); + } else { + print( " - complete\n" ); } - $cascade = !undef; + } + print( "Conversion complete\n" ); } - if ( $cascade || $version eq "1.24.4" ) - { - # Patch the database - patchDB( $dbh, "1.24.4" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.4" ) { +# Patch the database + patchDB( $dbh, "1.24.4" ); - # Copy the FTP specific values to the new general config - my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; - my $fetchSth = $dbh->prepare_cached( $fetchSql ) or die( "Can't prepare '$fetchSql': ".$dbh->errstr() ); - my $updateSql = "update Config set Value = ? where Name = ?"; - my $updateSth = $dbh->prepare_cached( $updateSql ) or die( "Can't prepare '$updateSql': ".$dbh->errstr() ); - my $fetchRes = $fetchSth->execute() or die( "Can't execute: ".$fetchSth->errstr() ); - while( my $config = $fetchSth->fetchrow_hashref() ) - { - ( my $name = $config->{Name} ) =~ s/_FTP_/_/; - my $updateRes = $updateSth->execute( $config->{Value}, $name ) or die( "Can't execute: ".$updateSth->errstr() ); - } - $cascade = !undef; +# Copy the FTP specific values to the new general config + my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; + my $fetchSth = $dbh->prepare_cached( $fetchSql ) or die( "Can't prepare '$fetchSql': ".$dbh->errstr() ); + my $updateSql = "update Config set Value = ? where Name = ?"; + my $updateSth = $dbh->prepare_cached( $updateSql ) or die( "Can't prepare '$updateSql': ".$dbh->errstr() ); + my $fetchRes = $fetchSth->execute() or die( "Can't execute: ".$fetchSth->errstr() ); + while( my $config = $fetchSth->fetchrow_hashref() ) { + ( my $name = $config->{Name} ) =~ s/_FTP_/_/; + my $updateRes = $updateSth->execute( $config->{Value}, $name ) or die( "Can't execute: ".$updateSth->errstr() ); } - if ( $cascade || $version lt "1.26.0" ) - { - my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); - die "Error: " . $dbh->errstr . "\n" unless ($sth); - die "Error: " . $sth->errstr . "\n" unless ($sth->execute); + $cascade = !undef; + } + if ( $cascade || $version lt "1.26.0" ) { + my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); + die "Error: " . $dbh->errstr . "\n" unless ($sth); + die "Error: " . $sth->errstr . "\n" unless ($sth->execute); - my $columns = $sth->{'NAME'}; - if ( ! grep(/^Colours$/, @$columns ) ) { - $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); - } # end if - if ( ! grep(/^Deinterlacing$/, @$columns ) ) { - $dbh->do(q{alter table Monitors add column `Deinterlacing` INT unsigned NOT NULL default '0' after `Orientation`;}); - } # end if - $sth->finish(); + my $columns = $sth->{'NAME'}; + if ( ! grep(/^Colours$/, @$columns ) ) { + $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); + } # end if + if ( ! grep(/^Deinterlacing$/, @$columns ) ) { + $dbh->do(q{alter table Monitors add column `Deinterlacing` INT unsigned NOT NULL default '0' after `Orientation`;}); + } # end if + $sth->finish(); + $cascade = !undef; + $version = '1.26.0'; + } - $cascade = !undef; - $version = '1.26.0'; - } + if ( $version ge '1.26.0' ) { - if ( $version ge '1.26.0' ) { + my @files; + $updateDir = $Config{ZM_PATH_DATA}."/db" if ! $updateDir; + opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; +#@files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); +#PP - use perl version sort + @files = sort { + my ($x) = ($a =~ m/^zm_update\-(.*)\.sql$/); + my ($y) = ($b =~ m/^zm_update\-(.*)\.sql$/); + version->parse($x) <=> version->parse($y) + } grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); + closedir $dh; + if ( ! @files ) { + die "Should have found upgrade scripts at $updateDir\n"; + } # end if - my @files; - $updateDir = $Config{ZM_PATH_DATA}."/db" if ! $updateDir; - opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; - #@files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); - #PP - use perl version sort - @files = sort { - my ($x) = ($a =~ m/^zm_update\-(.*)\.sql$/); - my ($y) = ($b =~ m/^zm_update\-(.*)\.sql$/); - version->parse($x) <=> version->parse($y) - } grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); - closedir $dh; - if ( ! @files ) { - die "Should have found upgrade scripts at $updateDir\n"; - } # end if - - $dbh->{'AutoCommit'} = 0; - foreach my $patch ( @files ) { - my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; - #PP make sure we use version compare - if ( version->parse('v' . $v) > version->parse('v' . $version) ) { - print( "Upgrading DB to $v from $version\n" ); - patchDB( $dbh, $v ); - if ( $dbh->errstr() ) { - $dbh->rollback(); - die "Error: " . $dbh->errstr(). ". Rolled back.\n"; - } # end if error - } # end if - } # end foreach patchfile - $dbh->{'AutoCommit'} = 1; - $cascade = !undef; - } # end if + $dbh->{'AutoCommit'} = 0; + foreach my $patch ( @files ) { + my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; +#PP make sure we use version compare + if ( version->parse('v' . $v) > version->parse('v' . $version) ) { + print( "Upgrading DB to $v from $version\n" ); + patchDB( $dbh, $v ); + if ( $dbh->errstr() ) { + $dbh->rollback(); + die "Error: " . $dbh->errstr(). ". Rolled back.\n"; + } # end if error + } # end if + } # end foreach patchfile + $dbh->{'AutoCommit'} = 1; + $cascade = !undef; + } # end if - if ( $cascade ) - { - my $installed_version = ZM_VERSION; - my $sql = "update Config set Value = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$installed_version", "ZM_DYN_DB_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); - $res = $sth->execute( "$installed_version", "ZM_DYN_CURR_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - else - { - zmDbDisconnect(); - die( "Can't find upgrade from version '$version'" ); - } - print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); + if ( $cascade ) { + my $installed_version = ZM_VERSION; + my $sql = "update Config set Value = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( "$installed_version", "ZM_DYN_DB_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); + $res = $sth->execute( "$installed_version", "ZM_DYN_CURR_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } else { + zmDbDisconnect(); + die( "Can't find upgrade from version '$version'" ); + } + print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); exit( 0 ); diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 8f551f79d..c5ef158ea 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -87,24 +87,19 @@ while( 1 ) { next if $monitor->{Function} eq 'None'; my $restart = 0; - # Prevent open handles building up if we have connect to shared memory - # Many of our error checks below do a next without closing the mem handle. - # zmMemInvalidate will just return of nothing is open, so we can just do this here. - zmMemInvalidate( $monitor ); - if ( zmMemVerify( $monitor ) - && zmMemRead( $monitor, "shared_data:valid" ) - ) - { + if ( zmMemVerify( $monitor ) ) { # Check we have got an image recently my $image_time = zmGetLastWriteTime( $monitor ); if ( !defined($image_time) ) { # Can't read from shared data Debug( "LastWriteTime is not defined." ); + zmMemInvalidate( $monitor ); next; } if ( !$image_time ) { # We can't get the last capture time so can't be sure it's died. Debug( "LastWriteTime is = $image_time." ); + zmMemInvalidate( $monitor ); next; } diff --git a/scripts/zmx10.pl.in b/scripts/zmx10.pl.in index 58064a89f..fab30c9af 100644 --- a/scripts/zmx10.pl.in +++ b/scripts/zmx10.pl.in @@ -474,7 +474,11 @@ sub loadTasks or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok +# Check shared memory ok + if ( !zmMemVerify( $monitor ) ) { + zmMemInvalidate( $monitor ); + next ; + } $monitor_hash{$monitor->{Id}} = $monitor; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9876e2a87..92a3027fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_zone.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) @@ -12,21 +12,19 @@ add_library(zm STATIC ${ZM_BIN_SRC_FILES}) add_executable(zmc zmc.cpp) add_executable(zma zma.cpp) add_executable(zmu zmu.cpp) -add_executable(zmf zmf.cpp) add_executable(zms zms.cpp) target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zma zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) -target_link_libraries(zmf zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) # Generate man files for the binaries destined for the bin folder -FOREACH(CBINARY zma zmc zmf zmu) +FOREACH(CBINARY zma zmc zmu) POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp zoneminder-${CBINARY} 8) -ENDFOREACH(CBINARY zma zmc zmf zmu) +ENDFOREACH(CBINARY zma zmc zmu) -install(TARGETS zmc zma zmu zmf RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(TARGETS zmc zma zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}") diff --git a/src/snprintf.cpp b/src/snprintf.cpp new file mode 100644 index 000000000..645534e9c --- /dev/null +++ b/src/snprintf.cpp @@ -0,0 +1,25 @@ +snprintf( swap_path, sizeof(swap_path), "%s/zmswap-m%d/zmswap-q%06d", config.path_swap, monitor->Id(), connkey ); + +int len = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()); + + + int swap_path_length = strlen(config.path_swap) + snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; // +1 for NULL terminator + + if ( connkey && playback_buffer > 0 ) { + + if ( swap_path_length + max_swap_len_suffix > PATH_MAX ) { + Error( "Swap Path is too long. %d > %d ", swap_path_length+max_swap_len_suffix, PATH_MAX ); + } else { + swap_path = (char *)malloc( swap_path_length+max_swap_len_suffix ); + Debug( 3, "Checking swap image path %s", config.path_swap ); + strncpy( swap_path, config.path_swap, swap_path_length ); + if ( checkSwapPath( swap_path, false ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-m%d", monitor->Id() ); + if ( checkSwapPath( swap_path, true ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-q%06d", connkey ); + if ( checkSwapPath( swap_path, true ) ) { + buffered_playback = true; + } + } + } + diff --git a/src/zm_buffer.h b/src/zm_buffer.h index bcc952dc6..6fa18984e 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -153,7 +153,7 @@ public: mHead = mTail = mStorage; else if ( level ) { - if ( (mHead-mStorage) > mSize ) + if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize ) { memcpy( mStorage, mHead, mSize ); mHead = mStorage; diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index 899d7822a..0ea7af373 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -20,33 +20,43 @@ #include "zm.h" #include "zm_camera.h" -Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - id( p_id ), - type( p_type ), - width( p_width), - height( p_height ), - colours( p_colours ), - subpixelorder( p_subpixelorder ), - brightness( p_brightness ), - hue( p_hue ), - colour( p_colour ), - contrast( p_contrast ), - capture( p_capture ) +Camera::Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + monitor_id( p_monitor_id ), + type( p_type ), + width( p_width), + height( p_height ), + colours( p_colours ), + subpixelorder( p_subpixelorder ), + brightness( p_brightness ), + hue( p_hue ), + colour( p_colour ), + contrast( p_contrast ), + capture( p_capture ), + record_audio( p_record_audio ) { pixels = width * height; imagesize = pixels * colours; - Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",id,width,height,colours,subpixelorder,capture); + Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",monitor_id,width,height,colours,subpixelorder,capture); /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ - if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 16) != 0) { - Fatal("Image size is not multiples of 16"); - } else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 16) != 0 || (imagesize % 12) != 0)) { - Fatal("Image size is not multiples of 12 and 16"); + if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 64) != 0) { + Fatal("Image size is not multiples of 64"); + } else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 64) != 0 || (imagesize % 12) != 0)) { + Fatal("Image size is not multiples of 12 and 64"); } } -Camera::~Camera() -{ +Camera::~Camera() { } +Monitor *Camera::getMonitor() { + if ( ! monitor ) + monitor = Monitor::Load( monitor_id, false, Monitor::QUERY ); + return monitor; +} + +void Camera::setMonitor( Monitor *p_monitor ) { + monitor = p_monitor; + monitor_id = monitor->Id(); +} diff --git a/src/zm_camera.h b/src/zm_camera.h index e56e68c55..4d991d495 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -25,6 +25,10 @@ #include "zm_image.h" +class Camera; + +#include "zm_monitor.h" + // // Abstract base class for cameras. This is intended just to express // common attributes @@ -34,7 +38,8 @@ class Camera protected: typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType; - int id; + unsigned int monitor_id; + Monitor * monitor; // Null on instantiation, set as soon as possible. SourceType type; unsigned int width; unsigned int height; @@ -42,17 +47,20 @@ protected: unsigned int subpixelorder; unsigned int pixels; unsigned int imagesize; - int brightness; - int hue; - int colour; - int contrast; - bool capture; + int brightness; + int hue; + int colour; + int contrast; + bool capture; + bool record_audio; public: - Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); virtual ~Camera(); - int getId() const { return( id ); } + unsigned int getId() const { return( monitor_id ); } + Monitor *getMonitor(); + void setMonitor( Monitor *p_monitor ); SourceType Type() const { return( type ); } bool IsLocal() const { return( type == LOCAL_SRC ); } bool IsRemote() const { return( type == REMOTE_SRC ); } @@ -73,11 +81,14 @@ public: virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); } bool CanCapture() const { return( capture ); } - + + bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); } + virtual int PrimeCapture() { return( 0 ); } virtual int PreCapture()=0; virtual int Capture( Image &image )=0; virtual int PostCapture()=0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0; }; #endif // ZM_CAMERA_H diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 6f4e59b6e..61c61bf5a 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -27,16 +27,13 @@ #include "zm_utils.h" -void zmLoadConfig() -{ +void zmLoadConfig() { FILE *cfg; char line[512]; - if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL ) - { + if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL ) { Fatal( "Can't open %s: %s", ZM_CONFIG, strerror(errno) ); } - while ( fgets( line, sizeof(line), cfg ) != NULL ) - { + while ( fgets( line, sizeof(line), cfg ) != NULL ) { char *line_ptr = line; // Trim off any cr/lf line endings @@ -53,16 +50,14 @@ void zmLoadConfig() // Remove trailing white space char *temp_ptr = line_ptr+strlen(line_ptr)-1; - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) - { + while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) { *temp_ptr-- = '\0'; temp_ptr--; } // Now look for the '=' in the middle of the line temp_ptr = strchr( line_ptr, '=' ); - if ( !temp_ptr ) - { + if ( !temp_ptr ) { Warning( "Invalid data in %s: '%s'", ZM_CONFIG, line ); continue; } @@ -72,12 +67,10 @@ void zmLoadConfig() char *val_ptr = temp_ptr+1; // Trim trailing space from the name part - do - { + do { *temp_ptr = '\0'; temp_ptr--; - } - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); + } while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); // Remove leading white space from the value part white_len = strspn( val_ptr, " \t" ); @@ -99,8 +92,7 @@ void zmLoadConfig() staticConfig.SERVER_NAME = std::string(val_ptr); else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 ) staticConfig.SERVER_ID = atoi(val_ptr); - else - { + else { // We ignore this now as there may be more parameters than the // c/c++ binaries are bothered about // Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG ); @@ -143,8 +135,7 @@ void zmLoadConfig() StaticConfig staticConfig; -ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type ) -{ +ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type ) { name = new char[strlen(p_name)+1]; strcpy( name, p_name ); value = new char[strlen(p_value)+1]; @@ -157,50 +148,37 @@ ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *con accessed = false; } -ConfigItem::~ConfigItem() -{ +ConfigItem::~ConfigItem() { delete[] name; delete[] value; delete[] type; } -void ConfigItem::ConvertValue() const -{ - if ( !strcmp( type, "boolean" ) ) - { +void ConfigItem::ConvertValue() const { + if ( !strcmp( type, "boolean" ) ) { cfg_type = CFG_BOOLEAN; cfg_value.boolean_value = (bool)strtol( value, 0, 0 ); - } - else if ( !strcmp( type, "integer" ) ) - { + } else if ( !strcmp( type, "integer" ) ) { cfg_type = CFG_INTEGER; cfg_value.integer_value = strtol( value, 0, 10 ); - } - else if ( !strcmp( type, "hexadecimal" ) ) - { + } else if ( !strcmp( type, "hexadecimal" ) ) { cfg_type = CFG_INTEGER; cfg_value.integer_value = strtol( value, 0, 16 ); - } - else if ( !strcmp( type, "decimal" ) ) - { + } else if ( !strcmp( type, "decimal" ) ) { cfg_type = CFG_DECIMAL; cfg_value.decimal_value = strtod( value, 0 ); - } - else - { + } else { cfg_type = CFG_STRING; cfg_value.string_value = value; } accessed = true; } -bool ConfigItem::BooleanValue() const -{ +bool ConfigItem::BooleanValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_BOOLEAN ) - { + if ( cfg_type != CFG_BOOLEAN ) { Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -208,13 +186,11 @@ bool ConfigItem::BooleanValue() const return( cfg_value.boolean_value ); } -int ConfigItem::IntegerValue() const -{ +int ConfigItem::IntegerValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_INTEGER ) - { + if ( cfg_type != CFG_INTEGER ) { Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -222,13 +198,11 @@ int ConfigItem::IntegerValue() const return( cfg_value.integer_value ); } -double ConfigItem::DecimalValue() const -{ +double ConfigItem::DecimalValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_DECIMAL ) - { + if ( cfg_type != CFG_DECIMAL ) { Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -236,13 +210,11 @@ double ConfigItem::DecimalValue() const return( cfg_value.decimal_value ); } -const char *ConfigItem::StringValue() const -{ +const char *ConfigItem::StringValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_STRING ) - { + if ( cfg_type != CFG_STRING ) { Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -250,80 +222,66 @@ const char *ConfigItem::StringValue() const return( cfg_value.string_value ); } -Config::Config() -{ +Config::Config() { n_items = 0; items = 0; } -Config::~Config() -{ - if ( items ) - { - for ( int i = 0; i < n_items; i++ ) - { +Config::~Config() { + if ( items ) { + for ( int i = 0; i < n_items; i++ ) { delete items[i]; } delete[] items; } } -void Config::Load() -{ +void Config::Load() { static char sql[ZM_SQL_SML_BUFSIZ]; strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(sql) ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } n_items = mysql_num_rows( result ); - if ( n_items <= ZM_MAX_CFG_ID ) - { + if ( n_items <= ZM_MAX_CFG_ID ) { Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items ); exit( -1 ); } items = new ConfigItem *[n_items]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] ); } mysql_free_result( result ); } -void Config::Assign() -{ +void Config::Assign() { ZM_CFG_ASSIGN_LIST } -const ConfigItem &Config::Item( int id ) -{ - if ( !n_items ) - { +const ConfigItem &Config::Item( int id ) { + if ( !n_items ) { Load(); Assign(); } - if ( id < 0 || id > ZM_MAX_CFG_ID ) - { + if ( id < 0 || id > ZM_MAX_CFG_ID ) { Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id ); exit( -1 ); } ConfigItem *item = items[id]; - if ( !item ) - { + if ( !item ) { Error( "Can't find config item %d", id ); exit( -1 ); } diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 3751088a8..3db890bf9 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -18,8 +18,11 @@ // #include "zm.h" + #include "zm_curl_camera.h" +#include "zm_packetqueue.h" + #if HAVE_LIBCURL #define CURL_MAXRETRY 5 @@ -30,28 +33,24 @@ const char* content_type_match = "Content-Type:"; size_t content_length_match_len; size_t content_type_match_len; -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, 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( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) { - if ( capture ) - { + if ( capture ) { Initialise(); } } -cURLCamera::~cURLCamera() -{ - if ( capture ) - { +cURLCamera::~cURLCamera() { + if ( capture ) { Terminate(); } } -void cURLCamera::Initialise() -{ +void cURLCamera::Initialise() { content_length_match_len = strlen(content_length_match); content_type_match_len = strlen(content_type_match); @@ -88,8 +87,7 @@ void cURLCamera::Initialise() } } -void cURLCamera::Terminate() -{ +void cURLCamera::Terminate() { /* Signal the thread to terminate */ bTerminate = true; @@ -108,20 +106,17 @@ void cURLCamera::Terminate() } -int cURLCamera::PrimeCapture() -{ +int cURLCamera::PrimeCapture() { //Info( "Priming capture from %s", mPath.c_str() ); return 0; } -int cURLCamera::PreCapture() -{ - // Nothing to do here - return( 0 ); +int cURLCamera::PreCapture() { + // Nothing to do here + return( 0 ); } -int cURLCamera::Capture( Image &image ) -{ +int cURLCamera::Capture( Image &image ) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -305,14 +300,18 @@ int cURLCamera::Capture( Image &image ) return 0; } -int cURLCamera::PostCapture() -{ +int cURLCamera::PostCapture() { + // Nothing to do here + return( 0 ); +} + +int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { + Error("Capture and Record not implemented for the cURL camera type"); // Nothing to do here return( 0 ); } -size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { lock(); /* Append the data we just received to our buffer */ @@ -331,10 +330,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * return size*nmemb; } - - -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; header.assign((const char*)buffer, size*nmemb); @@ -374,8 +370,7 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi return size*nmemb; } -void* cURLCamera::thread_func() -{ +void* cURLCamera::thread_func() { long tRet; double dSize; @@ -521,8 +516,7 @@ int cURLCamera::unlock() { return nRet; } -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 */ if(bTerminate) return -10; @@ -531,18 +525,15 @@ int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, } /* These functions call the functions in the class for the correct object */ -size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata); } -size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata); } -int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) -{ +int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow); } @@ -550,6 +541,4 @@ void* thread_func_dispatcher(void* object) { return ((cURLCamera*)object)->thread_func(); } - - #endif // HAVE_LIBCURL diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index 8a06428c6..c9dc2e935 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -39,8 +39,7 @@ // Class representing 'curl' cameras, i.e. those which are // accessed using the curl library // -class cURLCamera : public Camera -{ +class cURLCamera : public Camera { protected: typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t; @@ -65,7 +64,7 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, 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(); const std::string &Path() const { return( mPath ); } @@ -79,6 +78,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 00e4e38a0..e335dff3c 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -42,8 +42,6 @@ extern "C" #include "zm_sendfile.h" } -#include "zmf.h" - #if HAVE_SYS_SENDFILE_H #include #endif @@ -54,15 +52,18 @@ bool Event::initialised = false; char Event::capture_file_format[PATH_MAX]; char Event::analyse_file_format[PATH_MAX]; char Event::general_file_format[PATH_MAX]; +char Event::video_file_format[PATH_MAX]; int Event::pre_alarm_count = 0; Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } }; -Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) : +Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) : monitor( p_monitor ), start_time( p_start_time ), cause( p_cause ), - noteSetMap( p_noteSetMap ) + noteSetMap( p_noteSetMap ), + videoEvent( p_videoEvent ), + videowriter( NULL ) { if ( !initialised ) Initialise(); @@ -71,8 +72,7 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string createNotes( notes ); bool untimedEvent = false; - if ( !start_time.tv_sec ) - { + if ( !start_time.tv_sec ) { untimedEvent = true; gettimeofday( &start_time, 0 ); } @@ -84,15 +84,13 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string static char sql[ZM_SQL_MED_BUFSIZ]; struct tm *stime = localtime( &start_time.tv_sec ); - snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes, StateId ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str(), state_id ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't insert event: %s", mysql_error( &dbconn ) ); + snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes, StateId, Videoed ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', '%d' )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str(), state_id, videoEvent ); + if ( mysql_query( &dbconn, sql ) ) { + Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql ); exit( mysql_errno( &dbconn ) ); } id = mysql_insert_id( &dbconn ); - if ( untimedEvent ) - { + if ( untimedEvent ) { Warning( "Event %d has zero time, setting to current", id ); } end_time.tv_sec = 0; @@ -101,8 +99,10 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string tot_score = 0; max_score = 0; - if ( config.use_deep_storage ) - { + struct stat statbuf; + char id_file[PATH_MAX]; + + if ( config.use_deep_storage ) { char *path_ptr = path; path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", config.dir_events, monitor->Id() ); @@ -117,17 +117,13 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string char date_path[PATH_MAX] = ""; char time_path[PATH_MAX] = ""; char *time_path_ptr = time_path; - for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) - { + for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) { path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] ); - struct stat statbuf; errno = 0; if ( stat( path, &statbuf ) ) { - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Fatal( "Can't mkdir %s: %s", path, strerror(errno)); } } else { @@ -139,83 +135,116 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string else if ( i >= 3 ) time_path_ptr += snprintf( time_path_ptr, sizeof(time_path)-(time_path_ptr-time_path), "%s%02d", i>3?"/":"", dt_parts[i] ); } - char id_file[PATH_MAX]; // Create event id symlink snprintf( id_file, sizeof(id_file), "%s/.%d", date_path, id ); if ( symlink( time_path, id_file ) < 0 ) Fatal( "Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); - // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - else - { + } else { snprintf( path, sizeof(path), "%s/%d/%d", config.dir_events, monitor->Id(), id ); - - struct stat statbuf; + errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); } } - char id_file[PATH_MAX]; - // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - last_db_frame = 0; -} + } // deep storage or not -Event::~Event() -{ - if ( frames > last_db_frame ) - { - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + // Create empty id tag file + snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); + if ( FILE *id_fp = fopen( id_file, "w" ) ) + fclose( id_fp ); + else + Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); + + last_db_frame = 0; + + video_name[0] = 0; + + /* Save as video */ + + if ( monitor->GetOptVideoWriter() != 0 ) { + snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" ); + snprintf( video_file, sizeof(video_file), video_file_format, path, video_name ); + + /* X264 MP4 video writer */ + if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { +#if ZM_HAVE_VIDEOWRITER_X264MP4 + videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams()); +#else + Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); +#endif + } + + if ( videowriter != NULL ) { + /* Open the video stream */ + int nRet = videowriter->Open(); + if(nRet != 0) { + Error("Failed opening video stream"); + delete videowriter; + videowriter = NULL; + } + + snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" ); + snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name ); + + /* Create timecodes file */ + timecodes_fd = fopen(timecodes_file, "wb"); + if ( timecodes_fd == NULL ) { + Error("Failed creating timecodes file"); + } + } + } else { + /* No video object */ + videowriter = NULL; + } + +} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) + +Event::~Event() { + static char sql[ZM_SQL_MED_BUFSIZ]; + struct DeltaTimeval delta_time; + DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + + if ( frames > last_db_frame ) { Debug( 1, "Adding closing frame %d to DB", frames ); - static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )", id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } - static char sql[ZM_SQL_MED_BUFSIZ]; + /* Close the video file */ + if ( videowriter != NULL ) { + int nRet = videowriter->Close(); + if(nRet != 0) { + Error("Failed closing video stream"); + } + delete videowriter; + videowriter = NULL; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + /* Close the timecodes file */ + fclose(timecodes_fd); + timecodes_fd = NULL; + } - snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); - if ( mysql_query( &dbconn, sql ) ) - { + snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id ); + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Event::createNotes( std::string ¬es ) -{ +void Event::createNotes( std::string ¬es ) { notes.clear(); - for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) - { + for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) { notes += mapIter->first; notes += ": "; const StringSet &stringSet = mapIter->second; - for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) - { + for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) { if ( setIter != stringSet.begin() ) notes += ", "; notes += *setIter; @@ -225,208 +254,95 @@ void Event::createNotes( std::string ¬es ) int Event::sd = -1; -bool Event::OpenFrameSocket( int monitor_id ) -{ - if ( sd > 0 ) - { - close( sd ); - } - - sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { - Error( "Can't create socket: %s", strerror(errno) ); - return( false ); - } - - int socket_buffer_size = config.frame_socket_size; - if ( socket_buffer_size > 0 ) - { - if ( setsockopt( sd, SOL_SOCKET, SO_SNDBUF, &socket_buffer_size, sizeof(socket_buffer_size) ) < 0 ) - { - Error( "Can't get socket buffer size to %d, error = %s", socket_buffer_size, strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - } - - int flags; - if ( (flags = fcntl( sd, F_GETFL )) < 0 ) - { - Error( "Can't get socket flags, error = %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - flags |= O_NONBLOCK; - if ( fcntl( sd, F_SETFL, flags ) < 0 ) - { - Error( "Can't set socket flags, error = %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - - char sock_path[PATH_MAX] = ""; - snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); - - struct sockaddr_un addr; - - strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); - addr.sun_family = AF_UNIX; - - if ( connect( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)+1) < 0 ) - { - Warning( "Can't connect to frame server: %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - - Debug( 1, "Opened connection to frame server" ); - return( true ); -} - -bool Event::ValidateFrameSocket( int monitor_id ) -{ - if ( sd < 0 ) - { - return( OpenFrameSocket( monitor_id ) ); - } - return( true ); -} - -bool Event::SendFrameImage( const Image *image, bool alarm_frame ) -{ - if ( !ValidateFrameSocket( monitor->Id() ) ) - { - return( false ); - } - - static int jpg_buffer_size = 0; - static unsigned char jpg_buffer[ZM_MAX_IMAGE_SIZE]; - - image->EncodeJpeg( jpg_buffer, &jpg_buffer_size, (alarm_frame&&(config.jpeg_alarm_file_quality>config.jpeg_file_quality))?config.jpeg_alarm_file_quality:config.jpeg_file_quality ); - - static FrameHeader frame_header; - - frame_header.event_id = id; - if ( config.use_deep_storage ) - frame_header.event_time = start_time.tv_sec; - frame_header.frame_id = frames; - frame_header.alarm_frame = alarm_frame; - frame_header.image_length = jpg_buffer_size; - - struct iovec iovecs[2]; - iovecs[0].iov_base = &frame_header; - iovecs[0].iov_len = sizeof(frame_header); - iovecs[1].iov_base = jpg_buffer; - iovecs[1].iov_len = jpg_buffer_size; - - ssize_t writev_size = sizeof(frame_header)+jpg_buffer_size; - ssize_t writev_result = writev( sd, iovecs, sizeof(iovecs)/sizeof(*iovecs)); - if ( writev_result != writev_size ) - { - if ( writev_result < 0 ) - { - if ( errno == EAGAIN ) - { - Warning( "Blocking write detected" ); - } - else - { - Error( "Can't write frame: %s", strerror(errno) ); - close( sd ); - sd = -1; - } - } - else - { - Error( "Incomplete frame write: %zd of %zd bytes written", writev_result, writev_size ); - close( sd ); - sd = -1; - } - return( false ); - } - Debug( 1, "Wrote frame image, %d bytes", jpg_buffer_size ); - - return( true ); -} - -bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) -{ +bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) { Image* ImgToWrite; Image* ts_image = NULL; - if ( config.timestamp_on_capture ) // stash the image we plan to use in another pointer regardless if timestamped. + if ( !config.timestamp_on_capture ) // stash the image we plan to use in another pointer regardless if timestamped. { ts_image = new Image(*image); monitor->TimestampImage( ts_image, ×tamp ); ImgToWrite=ts_image; - } - else + } else ImgToWrite=image; - if ( !config.opt_frame_server || !SendFrameImage(ImgToWrite, alarm_frame) ) - { - int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default - ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write - } + int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default + ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write + if(ts_image) delete(ts_image); // clean up if used. return( true ); } -void Event::updateNotes( const StringSetMap &newNoteSetMap ) -{ +bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ) { + const Image* frameimg = image; + Image ts_image; + + /* Checking for invalid parameters */ + if ( videow == NULL ) { + Error("NULL Video object"); + return false; + } + + /* If the image does not contain a timestamp, add the timestamp */ + if (!config.timestamp_on_capture) { + ts_image = *image; + monitor->TimestampImage( &ts_image, ×tamp ); + frameimg = &ts_image; + } + + /* Calculate delta time */ + struct DeltaTimeval delta_time3; + DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 ); + unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; + + /* Encode and write the frame */ + if(videowriter->Encode(frameimg, timeMS) != 0) { + Error("Failed encoding video frame"); + } + + /* Add the frame to the timecodes file */ + fprintf(timecodes_fd, "%u\n", timeMS); + + return( true ); +} + +void Event::updateNotes( const StringSetMap &newNoteSetMap ) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); - if ( newNoteSetMap.size() > 0 ) - { - if ( noteSetMap.size() == 0 ) - { + if ( newNoteSetMap.size() > 0 ) { + if ( noteSetMap.size() == 0 ) { noteSetMap = newNoteSetMap; update = true; - } - else - { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) - { + } else { + for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); - if ( newNoteSet.size() > 0 ) - { + if ( newNoteSet.size() > 0 ) { StringSetMap::iterator noteSetMapIter = noteSetMap.find( newNoteGroup ); - if ( noteSetMapIter == noteSetMap.end() ) - { + if ( noteSetMapIter == noteSetMap.end() ) { //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); noteSetMap.insert( StringSetMap::value_type( newNoteGroup, newNoteSet ) ); update = true; - } - else - { + } else { StringSet ¬eSet = noteSetMapIter->second; //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) - { + for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) { const std::string &newNote = *newNoteSetIter; StringSet::iterator noteSetIter = noteSet.find( newNote ); - if ( noteSetIter == noteSet.end() ) - { + if ( noteSetIter == noteSet.end() ) { noteSet.insert( newNote ); update = true; } - } - } - } - } - } - } + } // end for + } // end if ( noteSetMap.size() == 0 + } // end if newNoteSetupMap.size() > 0 + } // end foreach newNoteSetMap + } // end if have old notes + } // end if have new notes - if ( update ) - { + if ( update ) { std::string notes; createNotes( notes ); @@ -438,19 +354,16 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) char notesStr[ZM_SQL_MED_BUFSIZ] = ""; unsigned long notesLen = 0; - if ( !stmt ) - { + if ( !stmt ) { const char *sql = "update Events set Notes = ? where Id = ?"; stmt = mysql_stmt_init( &dbconn ); - if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) - { + 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 ) - { + if ( mysql_stmt_param_count( stmt ) != 2 ) { Fatal( "Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count( stmt ), sql ); } @@ -470,8 +383,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) bind[1].length= 0; /* Bind the buffers */ - if ( mysql_stmt_bind_param( stmt, bind ) ) - { + if ( mysql_stmt_bind_param( stmt, bind ) ) { Fatal( "Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt) ); } } @@ -479,8 +391,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) strncpy( notesStr, notes.c_str(), sizeof(notesStr) ); notesLen = notes.length(); - if ( mysql_stmt_execute( stmt ) ) - { + if ( mysql_stmt_execute( stmt ) ) { Fatal( "Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt) ); } #else @@ -489,30 +400,25 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() ); snprintf( sql, sizeof(sql), "update Events set Notes = '%s' where Id = %d", escapedNotes, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert event: %s", mysql_error( &dbconn ) ); } #endif } } -void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) -{ +void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) { for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) { AddFramesInternal(n_frames, i, images, timestamps); } } -void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) -{ +void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) { static char sql[ZM_SQL_LGE_BUFSIZ]; strncpy( sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql) ); int frameCount = 0; - for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) - { - if ( !timestamps[i]->tv_sec ) - { + for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { + if ( !timestamps[i]->tv_sec ) { Debug( 1, "Not adding pre-capture frame %d, zero timestamp", i ); continue; } @@ -521,9 +427,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - - Debug( 1, "Writing pre-capture frame %d", frames ); - WriteFrameImage( images[i], *(timestamps[i]), event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( images[i], *(timestamps[i]), snapshot_file ); + } + } + if ( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing pre-capture frame %d", frames ); + WriteFrameImage( images[i], *(timestamps[i]), event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( images[i], *(timestamps[i]), videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 ); @@ -534,27 +452,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st frameCount++; } - if ( frameCount ) - { + if ( frameCount ) { Debug( 1, "Adding %d/%d frames to DB", frameCount, n_frames ); *(sql+strlen(sql)-2) = '\0'; - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frames: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; - } - else - { + } else { Debug( 1, "No valid pre-capture frames to add" ); } } -void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) -{ - if ( !timestamp.tv_sec ) - { +void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) { + if ( !timestamp.tv_sec ) { Debug( 1, "Not adding new frame, zero timestamp" ); return; } @@ -564,8 +476,21 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - Debug( 1, "Writing capture frame %d", frames ); - WriteFrameImage( image, timestamp, event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( image, timestamp, snapshot_file ); + } + } + if( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing capture frame %d", frames ); + WriteFrameImage( image, timestamp, event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( image, timestamp, videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 ); @@ -575,25 +500,30 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * score = 0; bool db_frame = (strcmp(frame_type,"Bulk") != 0) || ((frames%config.bulk_frame_interval)==0) || !frames; - if ( db_frame ) - { + if ( db_frame ) { Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type ); static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type, timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; // We are writing a Bulk frame - if ( !strcmp( frame_type,"Bulk") ) - { - snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( !strcmp( frame_type,"Bulk") ) { + snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", + ( delta_time.positive?"":"-" ), + delta_time.sec, delta_time.fsec, + frames, + alarm_frames, + tot_score, + (int)(alarm_frames?(tot_score/alarm_frames):0), + max_score, + id + ); + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -602,58 +532,48 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * end_time = timestamp; - // We are writing an Alarm frame - if ( !strcmp( frame_type,"Alarm") ) - { + // We are writing an Alarm frame + if ( !strcmp( frame_type,"Alarm") ) { alarm_frames++; tot_score += score; if ( score > (int)max_score ) max_score = score; - if ( alarm_image ) - { + if ( alarm_image ) { snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames ); Debug( 1, "Writing analysis frame %d", frames ); - WriteFrameImage( alarm_image, timestamp, event_file, true ); + if ( monitor->GetOptSaveJPEGs() & 2) { + WriteFrameImage( alarm_image, timestamp, event_file, true ); + } } } - + /* This makes viewing the diagnostic images impossible because it keeps deleting them - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { char diag_glob[PATH_MAX] = ""; snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", config.dir_events, monitor->Id() ); glob_t pglob; int glob_status = glob( diag_glob, 0, 0, &pglob ); - if ( glob_status != 0 ) - { - if ( glob_status < 0 ) - { + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { Error( "Can't glob '%s': %s", diag_glob, strerror(errno) ); - } - else - { + } else { Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status ); } - } - else - { + } else { char new_diag_path[PATH_MAX] = ""; - for ( int i = 0; i < pglob.gl_pathc; i++ ) - { + for ( int i = 0; i < pglob.gl_pathc; i++ ) { char *diag_path = pglob.gl_pathv[i]; char *diag_file = strstr( diag_path, "diag-" ); - if ( diag_file ) - { + if ( diag_file ) { snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file ); - if ( rename( diag_path, new_diag_path ) < 0 ) - { + if ( rename( diag_path, new_diag_path ) < 0 ) { Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) ); } } @@ -664,28 +584,24 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * */ } -bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) -{ +bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) { static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %d and unix_timestamp( EndTime ) > %ld order by Id asc limit 1", monitor_id, event_time ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -696,17 +612,13 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) loadEventData( init_event_id ); - if ( event_time ) - { + if ( event_time ) { curr_stream_time = event_time; curr_frame_id = 1; - if ( event_time >= event_data->start_time ) - { - for (unsigned int i = 0; i < event_data->frame_count; i++ ) - { + if ( event_time >= event_data->start_time ) { + for (unsigned int i = 0; i < event_data->frame_count; i++ ) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); - if ( event_data->frames[i].timestamp >= event_time ) - { + if ( event_data->frames[i].timestamp >= event_time ) { curr_frame_id = i+1; Debug( 3, "Set cst:%.2f", curr_stream_time ); Debug( 3, "Set cfid:%d", curr_frame_id ); @@ -719,51 +631,42 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) return( true ); } -bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) -{ +bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) { loadEventData( init_event_id ); - if ( init_frame_id ) - { + if ( init_frame_id ) { curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_frame_id = init_frame_id; - } - else - { + } else { curr_stream_time = event_data->start_time; } return( true ); } -bool EventStream::loadEventData( int event_id ) -{ +bool EventStream::loadEventData( int event_id ) { static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id ); + snprintf( sql, sizeof(sql), "SELECT MonitorId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo FROM Events WHERE Id = %d", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( !mysql_num_rows( result ) ) - { + if ( !mysql_num_rows( result ) ) { Fatal( "Unable to load event %d, not found in DB", event_id ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -772,39 +675,35 @@ bool EventStream::loadEventData( int event_id ) event_data = new EventData; event_data->event_id = event_id; event_data->monitor_id = atoi( dbrow[0] ); - event_data->start_time = atoi(dbrow[3]); - if ( config.use_deep_storage ) - { + event_data->start_time = atoi(dbrow[2]); + if ( config.use_deep_storage ) { struct tm *event_time = localtime( &event_data->start_time ); if ( config.dir_events[0] == '/' ) snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); else snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); - } - else - { + } else { if ( config.dir_events[0] == '/' ) snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", config.dir_events, event_data->monitor_id, event_data->event_id ); else snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_data->event_id ); } - event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]); - event_data->duration = atof(dbrow[4]); + event_data->frame_count = dbrow[1] == NULL ? 0 : atoi(dbrow[1]); + event_data->duration = atof(dbrow[3]); + strncpy( event_data->video_file, dbrow[4], sizeof( event_data->video_file )-1 ); updateFrameRate( (double)event_data->frame_count/event_data->duration ); mysql_free_result( result ); snprintf( sql, sizeof(sql), "select FrameId, unix_timestamp( `TimeStamp` ), Delta from Frames where EventId = %d order by FrameId asc", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -815,17 +714,14 @@ bool EventStream::loadEventData( int event_id ) int id, last_id = 0; time_t timestamp, last_timestamp = event_data->start_time; double delta, last_delta = 0.0; - while ( ( dbrow = mysql_fetch_row( result ) ) ) - { + while ( ( dbrow = mysql_fetch_row( result ) ) ) { id = atoi(dbrow[0]); timestamp = atoi(dbrow[1]); delta = atof(dbrow[2]); int id_diff = id - last_id; double frame_delta = (delta-last_delta)/id_diff; - if ( id_diff > 1 ) - { - for ( int i = last_id+1; i < id; i++ ) - { + if ( id_diff > 1 ) { + for ( int i = last_id+1; i < id; i++ ) { event_data->frames[i-1].timestamp = (time_t)(last_timestamp + ((i-last_id)*frame_delta)); event_data->frames[i-1].offset = (time_t)(event_data->frames[i-1].timestamp-event_data->start_time); event_data->frames[i-1].delta = frame_delta; @@ -840,8 +736,7 @@ bool EventStream::loadEventData( int event_id ) last_delta = delta; last_timestamp = timestamp; } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -853,8 +748,7 @@ bool EventStream::loadEventData( int event_id ) mysql_free_result( result ); - if ( forceEventChange || mode == MODE_ALL_GAPLESS ) - { + if ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else @@ -865,245 +759,237 @@ bool EventStream::loadEventData( int event_id ) return( true ); } -void EventStream::processCommand( const CmdMsg *msg ) -{ - Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ) +void EventStream::processCommand( const CmdMsg *msg ) { + Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) - { + switch( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : - { - Debug( 1, "Got PAUSE command" ); + { + Debug( 1, "Got PAUSE command" ); - // Set paused flag - paused = true; - replay_rate = ZM_RATE_BASE; - last_frame_sent = TV_2_FLOAT( now ); - break; - } + // Set paused flag + paused = true; + replay_rate = ZM_RATE_BASE; + last_frame_sent = TV_2_FLOAT( now ); + break; + } case CMD_PLAY : - { - Debug( 1, "Got PLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got PLAY command" ); + if ( paused ) + { + // Clear paused flag + paused = false; + } + + // If we are in single event mode and at the last frame, replay the current event + if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) + curr_frame_id = 1; + + replay_rate = ZM_RATE_BASE; + break; } - - // If we are in single event mode and at the last frame, replay the current event - if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) - curr_frame_id = 1; - - replay_rate = ZM_RATE_BASE; - break; - } case CMD_VARPLAY : - { - Debug( 1, "Got VARPLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got VARPLAY command" ); + if ( paused ) + { + // Clear paused flag + paused = false; + } + replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; + break; } - replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; - break; - } case CMD_STOP : - { - Debug( 1, "Got STOP command" ); + { + Debug( 1, "Got STOP command" ); - // Clear paused flag - paused = false; - break; - } + // Clear paused flag + paused = false; + break; + } case CMD_FASTFWD : - { - Debug( 1, "Got FAST FWD command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got FAST FWD command" ); + if ( paused ) { + // Clear paused flag + paused = false; + } + // Set play rate + switch ( replay_rate ) { + case 2 * ZM_RATE_BASE : + replay_rate = 5 * ZM_RATE_BASE; + break; + case 5 * ZM_RATE_BASE : + replay_rate = 10 * ZM_RATE_BASE; + break; + case 10 * ZM_RATE_BASE : + replay_rate = 25 * ZM_RATE_BASE; + break; + case 25 * ZM_RATE_BASE : + case 50 * ZM_RATE_BASE : + replay_rate = 50 * ZM_RATE_BASE; + break; + default : + replay_rate = 2 * ZM_RATE_BASE; + break; + } + break; } - // Set play rate - switch ( replay_rate ) - { - case 2 * ZM_RATE_BASE : - replay_rate = 5 * ZM_RATE_BASE; - break; - case 5 * ZM_RATE_BASE : - replay_rate = 10 * ZM_RATE_BASE; - break; - case 10 * ZM_RATE_BASE : - replay_rate = 25 * ZM_RATE_BASE; - break; - case 25 * ZM_RATE_BASE : - case 50 * ZM_RATE_BASE : - replay_rate = 50 * ZM_RATE_BASE; - break; - default : - replay_rate = 2 * ZM_RATE_BASE; - break; - } - break; - } case CMD_SLOWFWD : - { - Debug( 1, "Got SLOW FWD command" ); - // Set paused flag - paused = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = 1; - break; - } + { + Debug( 1, "Got SLOW FWD command" ); + // Set paused flag + paused = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = 1; + break; + } case CMD_SLOWREV : - { - Debug( 1, "Got SLOW REV command" ); - // Set paused flag - paused = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = -1; - break; - } + { + Debug( 1, "Got SLOW REV command" ); + // Set paused flag + paused = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = -1; + break; + } case CMD_FASTREV : - { - Debug( 1, "Got FAST REV command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got FAST REV command" ); + if ( paused ) { + // Clear paused flag + paused = false; + } + // Set play rate + switch ( replay_rate ) { + case -2 * ZM_RATE_BASE : + replay_rate = -5 * ZM_RATE_BASE; + break; + case -5 * ZM_RATE_BASE : + replay_rate = -10 * ZM_RATE_BASE; + break; + case -10 * ZM_RATE_BASE : + replay_rate = -25 * ZM_RATE_BASE; + break; + case -25 * ZM_RATE_BASE : + case -50 * ZM_RATE_BASE : + replay_rate = -50 * ZM_RATE_BASE; + break; + default : + replay_rate = -2 * ZM_RATE_BASE; + break; + } + break; } - // Set play rate - switch ( replay_rate ) - { - case -2 * ZM_RATE_BASE : - replay_rate = -5 * ZM_RATE_BASE; - break; - case -5 * ZM_RATE_BASE : - replay_rate = -10 * ZM_RATE_BASE; - break; - case -10 * ZM_RATE_BASE : - replay_rate = -25 * ZM_RATE_BASE; - break; - case -25 * ZM_RATE_BASE : - case -50 * ZM_RATE_BASE : - replay_rate = -50 * ZM_RATE_BASE; - break; - default : - replay_rate = -2 * ZM_RATE_BASE; - break; - } - break; - } case CMD_ZOOMIN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); - switch ( zoom ) { - case 100: - zoom = 150; - break; - case 150: - zoom = 200; - break; - case 200: - zoom = 300; - break; - case 300: - zoom = 400; - break; - case 400: - default : - zoom = 500; - break; + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); + switch ( zoom ) { + case 100: + zoom = 150; + break; + case 150: + zoom = 200; + break; + case 200: + zoom = 300; + break; + case 300: + zoom = 400; + break; + case 400: + default : + zoom = 500; + break; + } + break; } - break; - } case CMD_ZOOMOUT : - { - Debug( 1, "Got ZOOM OUT command" ); - switch ( zoom ) { - case 500: - zoom = 400; - break; - case 400: - zoom = 300; - break; - case 300: - zoom = 200; - break; - case 200: - zoom = 150; - break; - case 150: - default : - zoom = 100; - break; + Debug( 1, "Got ZOOM OUT command" ); + switch ( zoom ) { + case 500: + zoom = 400; + break; + case 400: + zoom = 300; + break; + case 300: + zoom = 200; + break; + case 200: + zoom = 150; + break; + case 150: + default : + zoom = 100; + break; + } + break; } - break; - } case CMD_PAN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got PAN command, to %d,%d", x, y ); - break; - } + { + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got PAN command, to %d,%d", x, y ); + break; + } case CMD_SCALE : - { - scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - Debug( 1, "Got SCALE command, to %d", scale ); - break; - } + { + scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + Debug( 1, "Got SCALE command, to %d", scale ); + break; + } case CMD_PREV : - { - Debug( 1, "Got PREV command" ); - if ( replay_rate >= 0 ) - curr_frame_id = 0; - else - curr_frame_id = event_data->frame_count+1; - paused = false; - forceEventChange = true; - break; - } + { + Debug( 1, "Got PREV command" ); + if ( replay_rate >= 0 ) + curr_frame_id = 0; + else + curr_frame_id = event_data->frame_count+1; + paused = false; + forceEventChange = true; + break; + } case CMD_NEXT : - { - Debug( 1, "Got NEXT command" ); - if ( replay_rate >= 0 ) - curr_frame_id = event_data->frame_count+1; - else - curr_frame_id = 0; - paused = false; - forceEventChange = true; - break; - } + { + Debug( 1, "Got NEXT command" ); + if ( replay_rate >= 0 ) + curr_frame_id = event_data->frame_count+1; + else + curr_frame_id = 0; + paused = false; + forceEventChange = true; + break; + } case CMD_SEEK : - { - int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); - Debug( 1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id ); - break; - } + { + int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); + Debug( 1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id ); + break; + } case CMD_QUERY : - { - Debug( 1, "Got QUERY command, sending STATUS" ); - break; - } - case CMD_QUIT : - { - Info ("User initiated exit - CMD_QUIT"); - break; - } + { + Debug( 1, "Got QUERY command, sending STATUS" ); + break; + } + case CMD_QUIT : + { + Info ("User initiated exit - CMD_QUIT"); + break; + } default : - { - // Do nothing, for now - } + { + // Do nothing, for now + } } struct { int event; @@ -1129,8 +1015,7 @@ void EventStream::processCommand( const CmdMsg *msg ) DataMsg status_msg; status_msg.msg_type = MSG_DATA_EVENT; memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); - if ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) - { + if ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -1144,49 +1029,39 @@ void EventStream::processCommand( const CmdMsg *msg ) updateFrameRate( (double)event_data->frame_count/event_data->duration ); } -void EventStream::checkEventLoaded() -{ +void EventStream::checkEventLoaded() { bool reload_event = false; static char sql[ZM_SQL_SML_BUFSIZ]; - if ( curr_frame_id <= 0 ) - { + if ( curr_frame_id <= 0 ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id < %ld order by Id desc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; - } - else if ( (unsigned int)curr_frame_id > event_data->frame_count ) - { + } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id > %ld order by Id asc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; } - if ( reload_event ) - { - if ( forceEventChange || mode != MODE_SINGLE ) - { + if ( reload_event ) { + if ( forceEventChange || mode != MODE_SINGLE ) { //Info( "SQL:%s", sql ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( dbrow ) - { + if ( dbrow ) { int event_id = atoi(dbrow[0]); Debug( 1, "Loading new event %d", event_id ); @@ -1198,9 +1073,7 @@ void EventStream::checkEventLoaded() else curr_frame_id = 1; Debug( 2, "New frame id = %d", curr_frame_id ); - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1209,9 +1082,7 @@ void EventStream::checkEventLoaded() } mysql_free_result( result ); forceEventChange = false; - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1221,32 +1092,41 @@ void EventStream::checkEventLoaded() } } -bool EventStream::sendFrame( int delta_us ) -{ +bool EventStream::sendFrame( int delta_us ) { Debug( 2, "Sending frame %d", curr_frame_id ); static char filepath[PATH_MAX]; static struct stat filestat; FILE *fdj = NULL; - - snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + + // 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 ( monitor->GetOptSaveJPEGs() & 1 ) { + snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + } else if ( monitor->GetOptSaveJPEGs() & 2 ) { + snprintf( filepath, sizeof(filepath), Event::analyse_file_format, event_data->path, curr_frame_id ); + if ( stat( filepath, &filestat ) < 0 ) { + Debug(1, "%s not found, dalling back to capture"); + snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + } + + } else { + Fatal("JPEGS not saved.zms is not capable of streaming jpegs from mp4 yet"); + return false; + } #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { + if ( type == STREAM_MPEG ) { Image image( filepath ); Image *send_image = prepareImage( &image ); - if ( !vid_stream ) - { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); } /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000 ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -1261,31 +1141,26 @@ bool EventStream::sendFrame( int delta_us ) if ( type != STREAM_JPEG ) send_raw = false; - if ( send_raw ) - { + if ( send_raw ) { fdj = fopen( filepath, "rb" ); - if ( !fdj ) - { + if ( !fdj ) { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } -#if HAVE_SENDFILE +#if HAVE_SENDFILE if( fstat(fileno(fdj),&filestat) < 0 ) { - Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); - return( false ); - } + Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); + return( false ); + } #else - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); #endif - } - else - { + } else { Image image( filepath ); Image *send_image = prepareImage( &image ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); break; @@ -1309,8 +1184,7 @@ bool EventStream::sendFrame( int delta_us ) } } - switch( type ) - { + switch( type ) { case STREAM_JPEG : fprintf( stdout, "Content-Type: image/jpeg\r\n" ); break; @@ -1326,34 +1200,33 @@ bool EventStream::sendFrame( int delta_us ) } - if(send_raw) { -#if HAVE_SENDFILE - fprintf( stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size ); - if(zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size) { - /* sendfile() failed, use standard way instead */ - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + if(send_raw) { +#if HAVE_SENDFILE + fprintf( stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size ); + if(zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size) { + /* sendfile() failed, use standard way instead */ + img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); + return( false ); + } + } +#else + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); return( false ); } +#endif + fclose(fdj); /* Close the file handle */ + } else { + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + Error( "Unable to send stream frame: %s", strerror(errno) ); + return( false ); + } } -#else - fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { - Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); - return( false ); - } -#endif - fclose(fdj); /* Close the file handle */ - } else { - fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { - Error( "Unable to send stream frame: %s", strerror(errno) ); - return( false ); - } - } - + fprintf( stdout, "\r\n\r\n" ); fflush( stdout ); } @@ -1361,8 +1234,7 @@ bool EventStream::sendFrame( int delta_us ) return( true ); } -void EventStream::runStream() -{ +void EventStream::runStream() { Event::Initialise(); openComms(); @@ -1374,15 +1246,13 @@ void EventStream::runStream() if ( type == STREAM_JPEG ) fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - if ( !event_data ) - { + if ( !event_data ) { sendTextFrame( "No event data found" ); exit( 0 ); } unsigned int delta_us = 0; - while( !zm_terminate ) - { + while( !zm_terminate ) { gettimeofday( &now, NULL ); while(checkCommandQueue()); @@ -1398,27 +1268,21 @@ void EventStream::runStream() //Info( "cst:%.2f", curr_stream_time ); //Info( "cfid:%d", curr_frame_id ); //Info( "fdt:%d", frame_data->timestamp ); - if ( !paused ) - { + if ( !paused ) { bool in_event = true; double time_to_event = 0; - if ( replay_rate > 0 ) - { + if ( replay_rate > 0 ) { time_to_event = event_data->frames[0].timestamp - curr_stream_time; if ( time_to_event > 0 ) in_event = false; - } - else if ( replay_rate < 0 ) - { + } else if ( replay_rate < 0 ) { time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; if ( time_to_event > 0 ) in_event = false; } - if ( !in_event ) - { + if ( !in_event ) { double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > 1 ) - { + if ( actual_delta_time > 1 ) { static char frame_text[64]; snprintf( frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event ); if ( !sendTextFrame( frame_text ) ) @@ -1436,31 +1300,24 @@ void EventStream::runStream() // Figure out if we should send this frame bool send_frame = false; - if ( !paused ) - { + if ( !paused ) { // If we are streaming and this frame is due to be sent - if ( ((curr_frame_id-1)%frame_mod) == 0 ) - { + if ( ((curr_frame_id-1)%frame_mod) == 0 ) { delta_us = (unsigned int)(frame_data->delta * 1000000); // if effective > base we should speed up frame delivery delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); // but must not exceed maxfps - delta_us = max(delta_us, 1000000 / maxfps); + delta_us = max(delta_us, 1000000 / maxfps); send_frame = true; } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; - } - else - { + } else { // We are paused, and doing nothing double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > MAX_STREAM_DELAY ) - { + if ( actual_delta_time > MAX_STREAM_DELAY ) { // Send keepalive Debug( 2, "Sending keepalive frame" ); send_frame = true; @@ -1473,20 +1330,16 @@ void EventStream::runStream() curr_stream_time = frame_data->timestamp; - if ( !paused ) - { + if ( !paused ) { curr_frame_id += replay_rate>0?1:-1; - if ( send_frame && type != STREAM_MPEG ) - { + if ( send_frame && type != STREAM_MPEG ) { Debug( 3, "dUs: %d", delta_us ); usleep( delta_us ); } - } - else - { + } else { usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); } - } + } // end while ! zm_terminate #if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) delete vid_stream; diff --git a/src/zm_event.h b/src/zm_event.h index 901e5dc52..d6dd154fa 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -37,6 +37,7 @@ #include "zm.h" #include "zm_image.h" #include "zm_stream.h" +#include "zm_video.h" class Zone; class Monitor; @@ -46,8 +47,7 @@ class Monitor; // // Class describing events, i.e. captured periods of activity. // -class Event -{ +class Event { friend class EventStream; protected: @@ -55,6 +55,7 @@ protected: static char capture_file_format[PATH_MAX]; static char analyse_file_format[PATH_MAX]; static char general_file_format[PATH_MAX]; + static char video_file_format[PATH_MAX]; protected: static int sd; @@ -66,8 +67,7 @@ public: protected: typedef enum { NORMAL, BULK, ALARM } FrameType; - struct PreAlarmData - { + struct PreAlarmData { Image *image; struct timeval timestamp; unsigned int score; @@ -84,24 +84,31 @@ protected: struct timeval end_time; std::string cause; StringSetMap noteSetMap; + bool videoEvent; int frames; int alarm_frames; unsigned int tot_score; unsigned int max_score; char path[PATH_MAX]; + VideoWriter* videowriter; + FILE* timecodes_fd; + char video_name[PATH_MAX]; + char video_file[PATH_MAX]; + char timecodes_name[PATH_MAX]; + char timecodes_file[PATH_MAX]; protected: int last_db_frame; protected: - static void Initialise() - { + static void Initialise() { if ( initialised ) return; snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits ); snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits ); snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits ); + snprintf( video_file_format, sizeof(video_file_format), "%%s/%%s"); initialised = true; } @@ -113,7 +120,7 @@ public: static bool ValidateFrameSocket( int ); public: - Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ); + Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false ); ~Event(); int Id() const { return( id ); } @@ -127,6 +134,7 @@ public: bool SendFrameImage( const Image *image, bool alarm_frame=false ); bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); + bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ); void updateNotes( const StringSetMap &stringSetMap ); @@ -137,28 +145,26 @@ private: void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); public: - static const char *getSubPath( struct tm *time ) - { + static const char *getSubPath( struct tm *time ) { static char subpath[PATH_MAX] = ""; snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); return( subpath ); } - static const char *getSubPath( time_t *time ) - { + static const char *getSubPath( time_t *time ) { return( Event::getSubPath( localtime( time ) ) ); } + char* getEventFile(void) { + return video_file; + } + public: - static int PreAlarmCount() - { + static int PreAlarmCount() { return( pre_alarm_count ); } - static void EmptyPreAlarmFrames() - { - if ( pre_alarm_count > 0 ) - { - for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) - { + static void EmptyPreAlarmFrames() { + if ( pre_alarm_count > 0 ) { + for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) { delete pre_alarm_data[i].image; delete pre_alarm_data[i].alarm_frame; } @@ -166,29 +172,24 @@ public: } pre_alarm_count = 0; } - static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) - { + static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) { pre_alarm_data[pre_alarm_count].image = new Image( *image ); pre_alarm_data[pre_alarm_count].timestamp = timestamp; pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) - { + if ( alarm_frame ) { pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame ); } pre_alarm_count++; } - void SavePreAlarmFrames() - { - for ( int i = 0; i < pre_alarm_count; i++ ) - { + void SavePreAlarmFrames() { + for ( int i = 0; i < pre_alarm_count; i++ ) { AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame ); } EmptyPreAlarmFrames(); } }; -class EventStream : public StreamBase -{ +class EventStream : public StreamBase { public: typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; @@ -201,16 +202,16 @@ protected: bool in_db; }; - struct EventData - { + struct EventData { unsigned long event_id; unsigned long monitor_id; unsigned long frame_count; - time_t start_time; - double duration; - char path[PATH_MAX]; - int n_frames; - FrameData *frames; + time_t start_time; + double duration; + char path[PATH_MAX]; + int n_frames; + FrameData *frames; + char video_file[PATH_MAX]; }; protected: @@ -238,8 +239,7 @@ protected: bool sendFrame( int delta_us ); public: - EventStream() - { + EventStream() { mode = DEFAULT_MODE; forceEventChange = false; @@ -249,18 +249,15 @@ public: event_data = 0; } - void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) - { + void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) { loadInitialEventData( init_event_id, init_frame_id ); loadMonitor( event_data->monitor_id ); } - void setStreamStart( int monitor_id, time_t event_time ) - { + void setStreamStart( int monitor_id, time_t event_time ) { loadInitialEventData( monitor_id, event_time ); loadMonitor( monitor_id ); } - void setStreamMode( StreamMode p_mode ) - { + void setStreamMode( StreamMode p_mode ) { mode = p_mode; } void runStream(); diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index d088477a8..3cccdf3e8 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -23,6 +23,16 @@ #if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE +void FFMPEGInit() { + static bool bInit = false; + + if(!bInit) { + av_register_all(); + av_log_set_level(AV_LOG_DEBUG); + bInit = true; + } +} + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; @@ -31,40 +41,40 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp switch(p_colours) { case ZM_COLOUR_RGB24: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } case ZM_COLOUR_RGB32: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; + pf = AV_PIX_FMT_GRAY8; + break; default: - Panic("Unexpected colours: %d",p_colours); - pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ - break; + Panic("Unexpected colours: %d",p_colours); + pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ + break; } return pf; @@ -148,25 +158,17 @@ SWScale::SWScale() : gotdefaults(false), swscale_ctx(NULL), input_avframe(NULL), SWScale::~SWScale() { /* Free up everything */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &input_avframe ); -#else - av_freep( &input_avframe ); -#endif //input_avframe = NULL; -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &output_avframe ); -#else - av_freep( &output_avframe ); -#endif //output_avframe = NULL; if(swscale_ctx) { sws_freeContext(swscale_ctx); swscale_ctx = NULL; } - + Debug(4,"SWScale object destroyed"); } @@ -189,11 +191,11 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint Error("NULL Input or output buffer"); return -1; } - if(in_pf == 0 || out_pf == 0) { - Error("Invalid input or output pixel formats"); - return -2; - } - if(!width || !height) { + // if(in_pf == 0 || out_pf == 0) { + // Error("Invalid input or output pixel formats"); + // return -2; + // } + if (!width || !height) { Error("Invalid width or height"); return -3; } @@ -223,24 +225,37 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint #else size_t outsize = avpicture_get_size(out_pf, width, height); #endif + if(outsize < out_buffer_size) { Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size); return -5; } /* Get the context */ - swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL ); + swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL ); if(swscale_ctx == NULL) { Error("Failed getting swscale context"); return -6; } /* Fill in the buffers */ - if(!avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer, in_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, + (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, + in_pf, width, height) <= 0) { +#endif Error("Failed filling input frame with input buffer"); return -7; } - if(!avpicture_fill( (AVPicture*)output_avframe, out_buffer, out_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, + out_buffer, out_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, width, + height) <= 0) { +#endif Error("Failed filling output frame with output buffer"); return -8; } @@ -291,3 +306,206 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si #endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE + +#if HAVE_LIBAVUTIL +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ + int64_t a, b, this_thing; + + av_assert0(in_ts != AV_NOPTS_VALUE); + av_assert0(duration >= 0); + + if (*last == AV_NOPTS_VALUE || !duration || in_tb.num*(int64_t)out_tb.den <= out_tb.num*(int64_t)in_tb.den) { +simple_round: + *last = av_rescale_q(in_ts, in_tb, fs_tb) + duration; + return av_rescale_q(in_ts, in_tb, out_tb); + } + + a = av_rescale_q_rnd(2*in_ts-1, in_tb, fs_tb, AV_ROUND_DOWN) >>1; + b = (av_rescale_q_rnd(2*in_ts+1, in_tb, fs_tb, AV_ROUND_UP )+1)>>1; + if (*last < 2*a - b || *last > 2*b - a) + goto simple_round; + + this_thing = av_clip64(*last, a, b); + *last = this_thing + duration; + + return av_rescale_q(this_thing, fs_tb, out_tb); +} +#endif + +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { + AVFormatContext *s = avformat_alloc_context(); + int ret = 0; + + *avctx = NULL; + if (!s) { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (ret) { + avformat_free_context(s); + return ret; + } else { + s->oformat = oformat; + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (s->priv_data) { + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data= s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } + } else { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + s->priv_data = NULL; + } + + if (filename) strncpy(s->filename, filename, sizeof(s->filename)); + *avctx = s; + return 0; + } +} + +static void zm_log_fps(double d, const char *postfix) { + uint64_t v = lrintf(d * 100); + if (!v) { + Debug(1, "%1.4f %s", d, postfix); + } else if (v % 100) { + Debug(1, "%3.2f %s", d, postfix); + } else if (v % (100 * 1000)) { + Debug(1, "%1.0f %s", d, postfix); + } else + Debug(1, "%1.0fk %s", d / 1000, postfix); +} + +/* "user interface" functions */ +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) { + char buf[256]; + Debug(1, "Dumping stream index i(%d) index(%d)", i, index ); + int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); + AVStream *st = ic->streams[i]; + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); + + avcodec_string(buf, sizeof(buf), st->codec, is_output); + Debug(1, " Stream #%d:%d", index, i); + + /* the pid is an important information, so we display it */ + /* XXX: add a generic system */ + if (flags & AVFMT_SHOW_IDS) + Debug(1, "[0x%x]", st->id); + if (lang) + Debug(1, "(%s)", lang->value); + Debug(1, ", %d, %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den); + Debug(1, ": %s", buf); + + if (st->sample_aspect_ratio.num && // default + av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) { + AVRational display_aspect_ratio; + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codec->width * (int64_t)st->sample_aspect_ratio.num, + st->codec->height * (int64_t)st->sample_aspect_ratio.den, + 1024 * 1024); + Debug(1, ", SAR %d:%d DAR %d:%d", + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, + display_aspect_ratio.num, display_aspect_ratio.den); + } + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; + int tbn = st->time_base.den && st->time_base.num; + int tbc = st->codec->time_base.den && st->codec->time_base.num; + + if (fps || tbn || tbc) + Debug(3, "\n" ); + + if (fps) + zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps"); + if (tbn) + zm_log_fps(1 / av_q2d(st->time_base), tbc ? "stream tb numerator , " : "stream tb numerator"); + if (tbc) + zm_log_fps(1 / av_q2d(st->codec->time_base), "codec time base:"); + } + + if (st->disposition & AV_DISPOSITION_DEFAULT) + Debug(1, " (default)"); + if (st->disposition & AV_DISPOSITION_DUB) + Debug(1, " (dub)"); + if (st->disposition & AV_DISPOSITION_ORIGINAL) + Debug(1, " (original)"); + if (st->disposition & AV_DISPOSITION_COMMENT) + Debug(1, " (comment)"); + if (st->disposition & AV_DISPOSITION_LYRICS) + Debug(1, " (lyrics)"); + if (st->disposition & AV_DISPOSITION_KARAOKE) + Debug(1, " (karaoke)"); + if (st->disposition & AV_DISPOSITION_FORCED) + Debug(1, " (forced)"); + if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) + Debug(1, " (hearing impaired)"); + if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) + Debug(1, " (visual impaired)"); + if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) + Debug(1, " (clean effects)"); + Debug(1, "\n"); + + //dump_metadata(NULL, st->metadata, " "); + + //dump_sidedata(NULL, st, " "); +} + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { + const enum AVSampleFormat *p = codec->sample_fmts; + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + else Debug(2, "Not %s", av_get_sample_fmt_name( *p ) ); + p++; + } + return 0; +} + +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) +#else +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { + dst->data = reinterpret_cast(new uint64_t[(src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1]); + memcpy(dst->data, src->data, src->size ); + return 0; +} +#endif + diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index da3c33a76..f94c575f5 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -29,6 +29,7 @@ extern "C" { // AVUTIL #if HAVE_LIBAVUTIL_AVUTIL_H +#include "libavutil/avassert.h" #include #include #include @@ -40,8 +41,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0) #include @@ -58,55 +59,55 @@ extern "C" { #include #include #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - + #if defined(HAVE_LIBAVUTIL_AVUTIL_H) #if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) - #define _AVPIXELFORMAT AVPixelFormat + #define _AVPIXELFORMAT AVPixelFormat #else - #define _AVPIXELFORMAT PixelFormat - #define AV_PIX_FMT_NONE PIX_FMT_NONE - #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 - #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 - #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 - #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 - #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 - #define AV_PIX_FMT_BGRA PIX_FMT_BGRA - #define AV_PIX_FMT_ARGB PIX_FMT_ARGB - #define AV_PIX_FMT_ABGR PIX_FMT_ABGR - #define AV_PIX_FMT_RGBA PIX_FMT_RGBA - #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 - #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P - #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P - #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P - #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P - #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P - #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P - #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 - #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 - #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 - #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 - #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 - #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE - #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 - #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 - #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE - #define AV_PIX_FMT_NV12 PIX_FMT_NV12 - #define AV_PIX_FMT_NV21 PIX_FMT_NV21 - #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 - #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 - #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE - #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE - #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P - #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P - #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P - //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 - //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 - //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 + #define _AVPIXELFORMAT PixelFormat + #define AV_PIX_FMT_NONE PIX_FMT_NONE + #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 + #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 + #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 + #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 + #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 + #define AV_PIX_FMT_BGRA PIX_FMT_BGRA + #define AV_PIX_FMT_ARGB PIX_FMT_ARGB + #define AV_PIX_FMT_ABGR PIX_FMT_ABGR + #define AV_PIX_FMT_RGBA PIX_FMT_RGBA + #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 + #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 + #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P + #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P + #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P + #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P + #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P + #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P + #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 + #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 + #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 + #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 + #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 + #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE + #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 + #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 + #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE + #define AV_PIX_FMT_NV12 PIX_FMT_NV12 + #define AV_PIX_FMT_NV21 PIX_FMT_NV21 + #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 + #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 + #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE + #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE + #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P + #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P + #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P + //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 + //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 + //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 #endif #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ @@ -121,8 +122,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVCODEC_H #include @@ -130,9 +131,9 @@ extern "C" { #if defined(HAVE_LIBAVCODEC_AVCODEC_H) #if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) - #define _AVCODECID AVCodecID + #define _AVCODECID AVCodecID #else - #define _AVCODECID CodecID + #define _AVCODECID CodecID #endif #endif /* HAVE_LIBAVCODEC_AVCODEC_H */ @@ -146,8 +147,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVFORMAT_H #include @@ -162,8 +163,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVDEVICE_H #include @@ -178,8 +179,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_SWSCALE_H #include @@ -199,6 +200,9 @@ extern "C" { #endif #endif +/* A single function to initialize ffmpeg, to avoid multiple initializations */ +void FFMPEGInit(); + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); #endif // HAVE_LIBAVUTIL @@ -208,23 +212,23 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: - SWScale(); - ~SWScale(); - int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); - int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); - int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + SWScale(); + ~SWScale(); + int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); + int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); + int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); protected: - bool gotdefaults; - struct SwsContext* swscale_ctx; - AVFrame* input_avframe; - AVFrame* output_avframe; - enum _AVPIXELFORMAT default_input_pf; - enum _AVPIXELFORMAT default_output_pf; - unsigned int default_width; - unsigned int default_height; + bool gotdefaults; + struct SwsContext* swscale_ctx; + AVFrame* input_avframe; + AVFrame* output_avframe; + enum _AVPIXELFORMAT default_input_pf; + enum _AVPIXELFORMAT default_output_pf; + unsigned int default_width; + unsigned int default_height; }; #endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL @@ -261,19 +265,19 @@ protected: */ #ifdef __cplusplus - inline static const std::string av_make_error_string(int errnum) - { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; + inline static const std::string av_make_error_string(int errnum) + { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; #if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0) - av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); + av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); #else - snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); + snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); #endif - return (std::string)errbuf; - } + return (std::string)errbuf; + } - #undef av_err2str - #define av_err2str(errnum) av_make_error_string(errnum).c_str() + #undef av_err2str + #define av_err2str(errnum) av_make_error_string(errnum).c_str() /* The following is copied directly from newer ffmpeg */ #if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) @@ -288,4 +292,60 @@ protected: #endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) +#ifndef avformat_alloc_output_context2 +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename); +#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a) +#endif + +#ifndef av_rescale_delta +/** + * Rescale a timestamp while preserving known durations. + */ +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb); +#endif + +#ifndef av_clip64 +/** + * Clip a signed 64bit integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax) +{ + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +#define av_clip64 av_clip64_c +#endif + +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) + #define zm_av_packet_unref( packet ) av_packet_unref( packet ) + #define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) +#else + #define zm_av_packet_unref( packet ) av_free_packet( packet ) +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); +#endif +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) +#else + #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) +#endif + +#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + #define zm_av_frame_alloc() av_frame_alloc() +#else + #define zm_av_frame_alloc() avcodec_alloc_frame() +#endif + +#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + #define av_frame_free( input_avframe ) av_freep( input_avframe ) +#endif + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); + #endif // ZM_FFMPEG_H diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 5fd7ea382..7ebbeae07 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -23,6 +23,9 @@ #include "zm_ffmpeg_camera.h" +extern "C" { +#include "libavutil/time.h" +} #ifndef AV_ERROR_MAX_STRING_SIZE #define AV_ERROR_MAX_STRING_SIZE 64 #endif @@ -33,29 +36,34 @@ #include #endif -FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) { - if ( capture ) - { + if ( capture ) { Initialise(); } - + mFormatContext = NULL; mVideoStreamId = -1; - mCodecContext = NULL; - mCodec = NULL; + mAudioStreamId = -1; + mVideoCodecContext = NULL; + mAudioCodecContext = NULL; + mVideoCodec = NULL; + mAudioCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; mIsOpening = false; mCanCapture = false; mOpenStart = 0; mReopenThread = 0; - + videoStore = NULL; + video_last_pts = 0; + #if HAVE_LIBSWSCALE mConvertContext = NULL; #endif @@ -72,35 +80,37 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + } -FfmpegCamera::~FfmpegCamera() -{ +FfmpegCamera::~FfmpegCamera() { + + if ( videoStore ) { + delete videoStore; + } CloseFfmpeg(); - if ( capture ) - { + if ( capture ) { Terminate(); } } -void FfmpegCamera::Initialise() -{ +void FfmpegCamera::Initialise() { if ( logDebugging() ) av_log_set_level( AV_LOG_DEBUG ); else av_log_set_level( AV_LOG_QUIET ); av_register_all(); + avformat_network_init(); } -void FfmpegCamera::Terminate() -{ +void FfmpegCamera::Terminate() { } -int FfmpegCamera::PrimeCapture() -{ +int FfmpegCamera::PrimeCapture() { + mVideoStreamId = -1; + mAudioStreamId = -1; Info( "Priming capture from %s", mPath.c_str() ); if (OpenFfmpeg() != 0){ @@ -115,109 +125,102 @@ int FfmpegCamera::PreCapture() return( 0 ); } -int FfmpegCamera::Capture( Image &image ) -{ +int FfmpegCamera::Capture( Image &image ) { if (!mCanCapture){ return -1; } - + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. if (mReopenThread != 0) { void *retval = 0; int ret; - + ret = pthread_join(mReopenThread, &retval); if (ret != 0){ Error("Could not join reopen thread."); } - + Info( "Successfully reopened stream." ); mReopenThread = 0; } - AVPacket packet; - uint8_t* directbuffer; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if(directbuffer == NULL) { - Error("Failed requesting writeable buffer for the captured image."); - return (-1); - } - int frameComplete = false; - while ( !frameComplete ) - { + while ( !frameComplete ) { int avResult = av_read_frame( mFormatContext, &packet ); - if ( avResult < 0 ) - { + if ( avResult < 0 ) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE); if ( - // Check if EOF. - (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || - // Check for Connection failure. - (avResult == -110) - ) - { - Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + // Check if EOF. + (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (avResult == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf ); ReopenFfmpeg(); } Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf ); return( -1 ); } - Debug( 5, "Got packet from stream %d", packet.stream_index ); - if ( packet.stream_index == mVideoStreamId ) - { -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 ) -#else - if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 ) -#endif + Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts ); + // What about audio stream? Maybe someday we could do sound detection... + if ( packet.stream_index == mVideoStreamId ) { + int ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) Fatal( "Unable to decode frame at frame %d", frameCount ); Debug( 4, "Decoded video packet at frame %d", frameCount ); - if ( frameComplete ) - { - Debug( 3, "Got frame %d", frameCount ); + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); + directbuffer, imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); + imagePixFormat, width, height); #endif - + #if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + if(mConvertContext == NULL) { + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, imagePixFormat, + SWS_BICUBIC, NULL, NULL, NULL); - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context for %s", mPath.c_str() ); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); + } + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) + Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); + Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE - - frameCount++; - } - } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } - return (0); -} -int FfmpegCamera::PostCapture() -{ + frameCount++; + } // end if frameComplete + } else { + Debug( 4, "Different stream_index %d", packet.stream_index ); + } // end if packet.stream_index == mVideoStreamId + zm_av_packet_unref( &packet ); + } // end while ! frameComplete + return (0); +} // FfmpegCamera::Capture + +int FfmpegCamera::PostCapture() { // Nothing to do here return( 0 ); } @@ -278,70 +281,111 @@ int FfmpegCamera::OpenFfmpeg() { mIsOpening = false; Debug ( 1, "Opened input" ); + Info( "Stream open %s", mPath.c_str() ); + + //FIXME can speed up initial analysis but need sensible parameters... + //mFormatContext->probesize = 32; + //mFormatContext->max_analyze_duration = 32; // Locate stream info from avformat_open_input #if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) Debug ( 1, "Calling av_find_stream_info" ); if ( av_find_stream_info( mFormatContext ) < 0 ) #else - Debug ( 1, "Calling avformat_find_stream_info" ); + Debug ( 1, "Calling avformat_find_stream_info" ); if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 ) #endif Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) ); + startTime = av_gettime();//FIXME here or after find_Stream_info Debug ( 1, "Got stream info" ); // Find first video stream present + // The one we want Might not be the first mVideoStreamId = -1; - for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) - { + mAudioStreamId = -1; + for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { #else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) { #endif - { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + // if we break, then we won't find the audio stream + continue; + } else { + Debug(2, "Have another video stream." ); + } } - } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) { +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) { +#endif + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } + } + } // end foreach stream if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream in %s", mPath.c_str() ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() ); - Debug ( 1, "Found video stream" ); + Debug ( 3, "Found video stream at index %d", mVideoStreamId ); + Debug ( 3, "Found audio stream at index %d", mAudioStreamId ); - mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + // STolen from ispy + //this fixes issues with rtsp streams!! woot. + //mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode. + mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; // Try and get the codec from the codec context - if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL ) - Fatal( "Can't find codec for video stream from %s", mPath.c_str() ); - - Debug ( 1, "Found decoder" ); - + if ((mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL) { + Fatal("Can't find codec for video stream from %s", mPath.c_str()); + } else { + Debug(1, "Video Found decoder"); + zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) Debug ( 1, "Calling avcodec_open" ); - if ( avcodec_open( mCodecContext, mCodec ) < 0 ) + if (avcodec_open(mVideoCodecContext, mVideoCodec) < 0) #else - Debug ( 1, "Calling avcodec_open2" ); - if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 ) + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mVideoCodecContext, mVideoCodec, 0) < 0) #endif Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + + if ( mAudioStreamId >= 0 ) { + mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; + if ((mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL) { + Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); + } else { + Debug(1, "Audio Found decoder"); + zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); + // Open the codec +#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) + Debug ( 1, "Calling avcodec_open" ); + if (avcodec_open(mAudioCodecContext, mAudioCodec) < 0) +#else + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0) +#endif + Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + } Debug ( 1, "Opened codec" ); // Allocate space for the native video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mRawFrame = av_frame_alloc(); -#else - mRawFrame = avcodec_alloc_frame(); -#endif + mRawFrame = zm_av_frame_alloc(); // Allocate space for the converted video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mFrame = av_frame_alloc(); -#else - mFrame = avcodec_alloc_frame(); -#endif + mFrame = zm_av_frame_alloc(); if(mRawFrame == NULL || mFrame == NULL) Fatal( "Unable to allocate frame for %s", mPath.c_str() ); @@ -359,25 +403,37 @@ int FfmpegCamera::OpenFfmpeg() { } Debug ( 1, "Validated imagesize" ); - + #if HAVE_LIBSWSCALE Debug ( 1, "Calling sws_isSupportedInput" ); - if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { - Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff)); + if (!sws_isSupportedInput(mVideoCodecContext->pix_fmt)) { + Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff)); } - + if(!sws_isSupportedOutput(imagePixFormat)) { Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); } - + + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, + imagePixFormat, SWS_BICUBIC, NULL, + NULL, NULL); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); #else // HAVE_LIBSWSCALE Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE + if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) { + Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height ); + } + mCanCapture = true; return 0; -} +} // int FfmpegCamera::OpenFfmpeg() int FfmpegCamera::ReopenFfmpeg() { @@ -398,29 +454,26 @@ int FfmpegCamera::CloseFfmpeg(){ mCanCapture = false; -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &mFrame ); av_frame_free( &mRawFrame ); -#else - av_freep( &mFrame ); - av_freep( &mRawFrame ); -#endif - + #if HAVE_LIBSWSCALE - if ( mConvertContext ) - { + if ( mConvertContext ) { sws_freeContext( mConvertContext ); mConvertContext = NULL; } #endif - if ( mCodecContext ) - { - avcodec_close( mCodecContext ); - mCodecContext = NULL; // Freed by av_close_input_file + if (mVideoCodecContext) { + avcodec_close(mVideoCodecContext); + mVideoCodecContext = NULL; // Freed by av_close_input_file } - if ( mFormatContext ) - { + if (mAudioCodecContext) { + avcodec_close(mAudioCodecContext); + mAudioCodecContext = NULL; // Freed by av_close_input_file + } + + if ( mFormatContext ) { #if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) av_close_input_file( mFormatContext ); #else @@ -432,8 +485,7 @@ int FfmpegCamera::CloseFfmpeg(){ return 0; } -int FfmpegCamera::FfmpegInterruptCallback(void *ctx) -{ +int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { FfmpegCamera* camera = reinterpret_cast(ctx); if (camera->mIsOpening){ int now = time(NULL); @@ -469,4 +521,282 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){ } } +//Function to handle capture and store +int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) { + if ( ! mCanCapture ) { + return -1; + } + int ret; + static char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. + if ( mReopenThread != 0 ) { + void *retval = 0; + + ret = pthread_join(mReopenThread, &retval); + if (ret != 0){ + Error("Could not join reopen thread."); + } + + Info( "Successfully reopened stream." ); + mReopenThread = 0; + } + + if ( mVideoCodecContext->codec_id != AV_CODEC_ID_H264 ) { + Error( "Input stream is not h264. The stored event file may not be viewable in browser." ); + } + + int frameComplete = false; + while ( ! frameComplete ) { + av_init_packet( &packet ); + + ret = av_read_frame( mFormatContext, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + ReopenFfmpeg(); + } + + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return( -1 ); + } + + int key_frame = packet.flags & AV_PKT_FLAG_KEY; + + Debug( 4, "Got packet from stream %d packet pts (%d) dts(%d), key?(%d)", + packet.stream_index, packet.pts, packet.dts, + key_frame + ); + + //Video recording + if ( recording.tv_sec ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // for efficiency's sake, we should test for keyframe before we test for directory change... + if ( videoStore && key_frame && (strcmp(oldDirectory, event_file) != 0 ) ) { + // don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?... + // if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + + // I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it. + // Also don't know how much it matters for audio. + if ( packet.stream_index == mVideoStreamId ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + Warning("Error writing last packet to videostore."); + } + } // end if video + + delete videoStore; + videoStore = NULL; + } // end if end of recording + + if ( ( ! videoStore ) && key_frame && ( packet.stream_index == mVideoStreamId ) ) { + //Instantiate the video storage module + + if (record_audio) { + if (mAudioStreamId == -1) { + Debug(3, "Record Audio on but no audio stream found"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + + } else { + Debug(3, "Video module initiated with audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor()); + } + } else { + Debug(3, "Record_audio is false so exclude audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + } // end if record_audio + strcpy(oldDirectory, event_file); + + // Need to write out all the frames from the last keyframe? + // No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe. + unsigned int packet_count = 0; + ZMPacket *queued_packet; + + // Clear all packets that predate the moment when the recording began + packetqueue.clear_unwanted_packets( &recording, mVideoStreamId ); + + while ( ( queued_packet = packetqueue.popPacket() ) ) { + AVPacket *avp = queued_packet->av_packet(); + + packet_count += 1; + //Write the packet to our video store + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); + if ( avp->stream_index == mVideoStreamId ) { + ret = videoStore->writeVideoFramePacket( avp ); + } else if ( avp->stream_index == mAudioStreamId ) { + ret = videoStore->writeAudioFramePacket( avp ); + } else { + Warning("Unknown stream id in queued packet (%d)", avp->stream_index ); + ret = -1; + } + if ( ret < 0 ) { + //Less than zero and we skipped a frame + } + delete queued_packet; + } // end while packets in the packetqueue + Debug(2, "Wrote %d queued packets", packet_count ); + } // end if ! was recording + + } else { + // Not recording + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + + // Buffer video packets, since we are not recording. + // All audio packets are keyframes, so only if it's a video keyframe + if ( packet.stream_index == mVideoStreamId ) { + if ( key_frame ) { + Debug(3, "Clearing queue"); + packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId ); + } +#if 0 +// Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to. +// Also, I think there are integer wrap-around issues. + +else if ( packet.pts && video_last_pts > packet.pts ) { + Warning( "Clearing queue due to out of order pts packet.pts(%d) < video_last_pts(%d)"); + packetqueue.clearQueue(); + } +#endif + } + + // The following lines should ensure that the queue always begins with a video keyframe + if ( packet.stream_index == mAudioStreamId ) { +//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); + if ( record_audio && packetqueue.size() ) { + // if it's audio, and we are doing audio, and there is already something in the queue + packetqueue.queuePacket( &packet ); + } + } else if ( packet.stream_index == mVideoStreamId ) { + if ( key_frame || packetqueue.size() ) // it's a keyframe or we already have something in the queue + packetqueue.queuePacket( &packet ); + } + } // end if recording or not + + if ( packet.stream_index == mVideoStreamId ) { + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + Debug(4, "about to decode video" ); + +#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) + ret = avcodec_send_packet( mVideoCodecContext, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + frameComplete = 1; +# else + ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } +#endif + + Debug( 4, "Decoded video packet at frame %d", frameCount ); + + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if ( directbuffer == NULL ) { + Error("Failed requesting writeable buffer for the captured image."); + zm_av_packet_unref( &packet ); + return (-1); + } +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); +#endif + + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, + 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { + Fatal("Unable to convert raw format %u to target format %u at frame %d", + mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); + } + + frameCount++; + } else { + Debug( 3, "Not framecomplete after av_read_frame" ); + } // end if frameComplete + } else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams + if ( videoStore ) { + if ( record_audio ) { + Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index ); + //Write the packet to our video store + //FIXME no relevance of last key frame + int ret = videoStore->writeAudioFramePacket( &packet ); + if ( ret < 0 ) {//Less than zero and we skipped a frame + Warning("Failure to write audio packet."); + zm_av_packet_unref( &packet ); + return 0; + } + } else { + Debug(4, "Not doing recording of audio packet" ); + } + } else { + Debug(4, "Have audio packet, but not recording atm" ); + } + } else { +#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0) + Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codecpar->codec_type) ); +#else + Debug( 3, "Some other stream index %d", packet.stream_index ); +#endif + } + //if ( videoStore ) { + + // the packet contents are ref counted... when queuing, we allocate another packet and reference it with that one, so we should always need to unref here, which should not affect the queued version. + zm_av_packet_unref( &packet ); + //} + } // end while ! frameComplete + return (frameCount); +} // end FfmpegCamera::CaptureAndRecord + #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index c11be9b3c..d472909db 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -25,59 +25,82 @@ #include "zm_buffer.h" //#include "zm_utils.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'ffmpeg' cameras, i.e. those which are // accessed using ffmpeg multimedia framework // -class FfmpegCamera : public Camera -{ -protected: - std::string mPath; - std::string mMethod; - std::string mOptions; +class FfmpegCamera : public Camera { + protected: + std::string mPath; + std::string mMethod; + std::string mOptions; - int frameCount; + int frameCount; #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mVideoCodecContext; + AVCodecContext *mAudioCodecContext; + AVCodec *mVideoCodec; + AVCodec *mAudioCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; - int OpenFfmpeg(); - int ReopenFfmpeg(); - int CloseFfmpeg(); - static int FfmpegInterruptCallback(void *ctx); - static void* ReopenFfmpegThreadCallback(void *ctx); - bool mIsOpening; - bool mCanCapture; - int mOpenStart; - pthread_t mReopenThread; + // Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero. + int64_t audio_last_pts; + int64_t audio_last_dts; + int64_t video_last_pts; + int64_t video_last_dts; + + // Used to store the incoming packet, it will get copied when queued. + // We only ever need one at a time, so instead of constantly allocating + // and freeing this structure, we will just make it a member of the object. + AVPacket packet; + + int OpenFfmpeg(); + int ReopenFfmpeg(); + int CloseFfmpeg(); + static int FfmpegInterruptCallback(void *ctx); + static void* ReopenFfmpegThreadCallback(void *ctx); + bool mIsOpening; + bool mCanCapture; + int mOpenStart; + pthread_t mReopenThread; #endif // HAVE_LIBAVFORMAT + VideoStore *videoStore; + char oldDirectory[4096]; + unsigned int old_event_id; + zm_packetqueue packetqueue; + #if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext; + struct SwsContext *mConvertContext; #endif -public: - FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); - ~FfmpegCamera(); + int64_t startTime; - const std::string &Path() const { return( mPath ); } - const std::string &Options() const { return( mOptions ); } - const std::string &Method() const { return( mMethod ); } + public: + FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + ~FfmpegCamera(); - void Initialise(); - void Terminate(); + const std::string &Path() const { return( mPath ); } + const std::string &Options() const { return( mOptions ); } + const std::string &Method() const { return( mMethod ); } - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); + void Initialise(); + void Terminate(); + + int PrimeCapture(); + int PreCapture(); + int Capture( Image &image ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); + int PostCapture(); }; #endif // ZM_FFMPEG_CAMERA_H diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index dfb12d38a..b77628963 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -34,7 +34,7 @@ #include "zm.h" #include "zm_file_camera.h" -FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ) +FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ) { strncpy( path, p_path, sizeof(path) ); if ( capture ) @@ -87,5 +87,5 @@ int FileCamera::Capture( Image &image ) int FileCamera::PostCapture() { - return( 0 ); + return( 0 ); } diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 84d720050..6ad911755 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -23,6 +23,7 @@ #include "zm_camera.h" #include "zm_buffer.h" #include "zm_regexp.h" +#include "zm_packetqueue.h" #include @@ -36,7 +37,7 @@ protected: char path[PATH_MAX]; public: - FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~FileCamera(); const char *Path() const { return( path ); } @@ -46,6 +47,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 9c8c12fda..c6e54d20a 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -45,7 +45,6 @@ static short *r_v_table; static short *g_v_table; static short *g_u_table; static short *b_u_table; -__attribute__((aligned(16))) static const uint8_t movemask[16] = {0,4,8,12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; jpeg_compress_struct *Image::writejpg_ccinfo[101] = { 0 }; jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; @@ -75,8 +74,7 @@ static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8; /* Pointer to image buffer memory copy function */ imgbufcpy_fptr_t fptr_imgbufcpy; -Image::Image() -{ +Image::Image() { if ( !initialised ) Initialise(); width = 0; @@ -92,8 +90,7 @@ Image::Image() text[0] = '\0'; } -Image::Image( const char *filename ) -{ +Image::Image( const char *filename ) { if ( !initialised ) Initialise(); width = 0; @@ -159,14 +156,14 @@ Image::~Image() { /* Should be called as part of program shutdown to free everything */ void Image::Deinitialise() { if ( initialised ) { - /* - delete[] y_table; - delete[] uv_table; - delete[] r_v_table; - delete[] g_v_table; - delete[] g_u_table; - delete[] b_u_table; - */ + /* + delete[] y_table; + delete[] uv_table; + delete[] r_v_table; + delete[] g_v_table; + delete[] g_u_table; + delete[] b_u_table; + */ initialised = false; if ( readjpg_dcinfo ) { jpeg_destroy_decompress( readjpg_dcinfo ); @@ -196,6 +193,16 @@ void Image::Initialise() if(config.cpu_extensions && sseversion >= 20) { fptr_blend = &sse2_fastblend; /* SSE2 fast blend */ Debug(4,"Blend: Using SSE2 fast blend function"); + } else if(config.cpu_extensions && neonversion >= 1) { +#if defined(__aarch64__) + fptr_blend = &neon64_armv8_fastblend; /* ARM Neon (AArch64) fast blend */ + Debug(4,"Blend: Using ARM Neon (AArch64) fast blend function"); +#elif defined(__arm__) + fptr_blend = &neon32_armv7_fastblend; /* ARM Neon (AArch32) fast blend */ + Debug(4,"Blend: Using ARM Neon (AArch32) fast blend function"); +#else + Panic("Bug: Non ARM platform but neon present"); +#endif } else { fptr_blend = &std_fastblend; /* standard fast blend */ Debug(4,"Blend: Using fast blend function"); @@ -205,23 +212,39 @@ void Image::Initialise() Debug(4,"Blend: Using standard blend function"); } - __attribute__((aligned(16))) uint8_t blend1[16] = {142,255,159,91,88,227,0,52,37,80,152,97,104,252,90,82}; - __attribute__((aligned(16))) uint8_t blend2[16] = {129,56,136,96,119,149,94,29,96,176,1,144,230,203,111,172}; - __attribute__((aligned(16))) uint8_t blendres[16]; - __attribute__((aligned(16))) uint8_t blendexp[16] = {141,231,157,92,91,217,11,49,45,92,133,103,119,246,92,93}; /* Expected results for 12.5% blend */ - - (*fptr_blend)(blend1,blend2,blendres,16,12.5); - + __attribute__((aligned(64))) uint8_t blend1[128] = { + 86,58,54,63,149,62,209,34,148,46,186,176,9,236,193,254,113,146,228,220,123,164,92,98,9,72,67,156,63,118,96,167, + 48,224,106,176,201,245,223,219,198,50,100,31,68,77,33,76,166,90,254,128,191,82,84,32,3,171,147,248,14,196,141,179, + 79,237,121,11,132,37,194,225,45,171,169,167,56,64,193,85,147,33,97,221,94,97,90,44,191,248,65,8,17,240,167,207, + 224,23,71,74,81,1,46,110,227,94,163,170,55,155,52,147,224,154,237,35,255,26,229,11,223,242,118,155,82,37,189,2 + }; + __attribute__((aligned(64))) uint8_t blend2[128] = { + 92,188,203,118,121,231,252,218,126,88,80,72,123,16,91,131,109,0,57,56,95,204,74,8,137,94,6,69,18,146,229,194, + 146,230,13,146,95,48,185,65,162,47,152,172,184,111,245,143,247,105,49,42,89,37,145,255,221,200,103,80,98,39,14,227, + 227,46,46,59,248,7,83,20,157,79,36,161,237,55,77,175,232,200,38,170,198,239,89,19,82,88,130,120,203,184,141,117, + 228,140,150,107,103,195,74,130,42,11,150,70,176,204,198,188,38,252,174,104,128,106,31,17,141,231,62,104,179,29,143,130 + }; + __attribute__((aligned(64))) uint8_t blendexp[128] = { + 86,73,71,69,145,82,214,56,145,51,173,163,22,209,180,239,112,128,207,200,119,168,89,87,24,74,59,145,57,121,111,170, + 59,224,94,172,188,221,218,200,193,49,106,47,81,81,58,84,175,91,229,117,178,76,91,58,29,174,141,227,24,177,125,184, + 96,214,112,16,145,33,180,200,58,159,153,166,77,62,179,95,157,53,89,214,106,114,89,41,177,228,72,21,39,233,163,196, + 224,37,80,77,83,24,49,112,204,84,161,158,69,160,69,151,201,165,229,43,239,35,205,11,213,240,111,148,93,36,183,17 + }; + __attribute__((aligned(64))) uint8_t blendres[128]; + + /* Run the blend function */ + (*fptr_blend)(blend1,blend2,blendres,128,12.0); + /* Compare results with expected results */ - for(int i=0;i<16;i++) { + for(int i=0;i<128;i++) { if(abs(blendexp[i] - blendres[i]) > 3) { - Panic("Blend function failed self-test: Results differ from the expected results"); + Panic("Blend function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,blendexp[i],blendres[i]); } } - + fptr_delta8_rgb = &std_delta8_rgb; fptr_delta8_bgr = &std_delta8_bgr; - + /* Assign the delta functions */ if(config.cpu_extensions) { if(sseversion >= 35) { @@ -238,17 +261,27 @@ void Image::Initialise() fptr_delta8_bgra = &sse2_delta8_bgra; fptr_delta8_argb = &sse2_delta8_argb; fptr_delta8_abgr = &sse2_delta8_abgr; - /* - ** On some systems, the 4 SSE2 algorithms above might be a little slower than - ** the standard algorithms, especially on early Pentium 4 processors. - ** In that case, comment out the 4 lines above and uncomment the 4 lines below - */ - // fptr_delta8_rgba = &std_delta8_rgba; - // fptr_delta8_bgra = &std_delta8_bgra; - // fptr_delta8_argb = &std_delta8_argb; - // fptr_delta8_abgr = &std_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; Debug(4,"Delta: Using SSE2 delta functions"); + } else if(neonversion >= 1) { + /* ARM Neon available */ +#if defined(__aarch64__) + fptr_delta8_rgba = &neon64_armv8_delta8_rgba; + fptr_delta8_bgra = &neon64_armv8_delta8_bgra; + fptr_delta8_argb = &neon64_armv8_delta8_argb; + fptr_delta8_abgr = &neon64_armv8_delta8_abgr; + fptr_delta8_gray8 = &neon64_armv8_delta8_gray8; + Debug(4,"Delta: Using ARM Neon (AArch64) delta functions"); +#elif defined(__arm__) + fptr_delta8_rgba = &neon32_armv7_delta8_rgba; + fptr_delta8_bgra = &neon32_armv7_delta8_bgra; + fptr_delta8_argb = &neon32_armv7_delta8_argb; + fptr_delta8_abgr = &neon32_armv7_delta8_abgr; + fptr_delta8_gray8 = &neon32_armv7_delta8_gray8; + Debug(4,"Delta: Using ARM Neon (AArch32) delta functions"); +#else + Panic("Bug: Non ARM platform but neon present"); +#endif } else { /* No suitable SSE version available */ fptr_delta8_rgba = &std_delta8_rgba; @@ -267,24 +300,64 @@ void Image::Initialise() fptr_delta8_gray8 = &std_delta8_gray8; Debug(4,"Delta: CPU extensions disabled, using standard delta functions"); } - - /* Use SSSE3 deinterlace functions? */ - if(config.cpu_extensions && sseversion >= 35) { - fptr_deinterlace_4field_rgba = &ssse3_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &ssse3_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &ssse3_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &ssse3_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &ssse3_deinterlace_4field_gray8; - Debug(4,"Deinterlace: Using SSSE3 delta functions"); - } else { - fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; - Debug(4,"Deinterlace: Using standard delta functions"); + + __attribute__((aligned(64))) uint8_t delta8_1[128] = { + 221,22,234,254,8,140,15,28,166,13,203,56,92,250,79,225,19,59,241,145,253,33,87,204,97,168,229,180,3,108,205,177, + 41,108,65,149,4,87,16,240,56,50,135,64,153,3,219,214,239,55,169,180,167,45,243,56,191,119,145,250,102,145,73,32, + 207,213,189,167,147,83,217,30,113,51,142,125,219,97,60,5,135,195,95,133,21,197,150,82,134,93,198,97,97,49,117,24, + 242,253,242,5,190,71,182,1,0,69,25,181,139,84,242,79,150,158,29,215,98,100,245,16,86,165,18,98,46,100,139,19 + }; + __attribute__((aligned(64))) uint8_t delta8_2[128] = { + 236,22,153,161,50,141,15,130,89,251,33,5,140,201,225,194,138,76,248,89,25,26,29,93,250,251,48,157,41,126,140,152, + 170,177,134,14,234,99,3,105,217,76,38,233,89,30,93,48,234,40,202,80,184,4,250,71,183,249,76,78,184,148,185,120, + 137,214,238,57,50,93,29,60,99,207,40,15,43,28,177,118,60,231,90,47,198,251,250,241,212,114,249,17,95,161,216,218, + 51,178,137,161,213,108,35,72,65,24,5,176,110,15,0,2,137,58,0,133,197,1,122,169,175,33,223,138,37,114,52,186 + }; + __attribute__((aligned(64))) uint8_t delta8_gray8_exp[128] = { + 15,0,81,93,42,1,0,102,77,238,170,51,48,49,146,31,119,17,7,56,228,7,58,111,153,83,181,23,38,18,65,25, + 129,69,69,135,230,12,13,135,161,26,97,169,64,27,126,166,5,15,33,100,17,41,7,15,8,130,69,172,82,3,112,88, + 70,1,49,110,97,10,188,30,14,156,102,110,176,69,117,113,75,36,5,86,177,54,100,159,78,21,51,80,2,112,99,194, + 191,75,105,156,23,37,147,71,65,45,20,5,29,69,242,77,13,100,29,82,99,99,123,153,89,132,205,40,9,14,87,167 + }; + __attribute__((aligned(64))) uint8_t delta8_rgba_exp[32] = { + 13,11,189,60,41,68,112,28,84,66,68,48,14,30,91,36,24,54,113,101,41,90,39,82,107,47,46,80,69,102,130,21 + }; + __attribute__((aligned(64))) uint8_t delta8_gray8_res[128]; + __attribute__((aligned(64))) uint8_t delta8_rgba_res[32]; + + /* Run the delta8 grayscale function */ + (*fptr_delta8_gray8)(delta8_1,delta8_2,delta8_gray8_res,128); + + /* Compare results with expected results */ + for(int i=0;i<128;i++) { + if(abs(delta8_gray8_exp[i] - delta8_gray8_res[i]) > 7) { + Panic("Delta grayscale function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_gray8_exp[i],delta8_gray8_res[i]); + } } - + + /* Run the delta8 RGBA function */ + (*fptr_delta8_rgba)(delta8_1,delta8_2,delta8_rgba_res,32); + + /* Compare results with expected results */ + for(int i=0;i<32;i++) { + if(abs(delta8_rgba_exp[i] - delta8_rgba_res[i]) > 7) { + Panic("Delta RGBA function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_rgba_exp[i],delta8_rgba_res[i]); + } + } + + /* + SSSE3 deinterlacing functions were removed because they were usually equal + or slower than the standard code (compiled with -O2 or better) + The function is too complicated to be vectorized efficiently on SSSE3 + */ + fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; + fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; + fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; + fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; + fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; + Debug(4,"Deinterlace: Using standard functions"); + +#if defined(__i386__) && !defined(__x86_64__) /* Use SSE2 aligned memory copy? */ if(config.cpu_extensions && sseversion >= 20) { fptr_imgbufcpy = &sse2_aligned_memcpy; @@ -293,10 +366,14 @@ void Image::Initialise() fptr_imgbufcpy = &memcpy; Debug(4,"Image buffer copy: Using standard memcpy"); } - +#else + fptr_imgbufcpy = &memcpy; + Debug(4,"Image buffer copy: Using standard memcpy"); +#endif + /* Code below relocated from zm_local_camera */ Debug( 3, "Setting up static colour tables" ); - + y_table = y_table_global; uv_table = uv_table_global; r_v_table = r_v_table_global; @@ -304,63 +381,63 @@ void Image::Initialise() g_u_table = g_u_table_global; b_u_table = b_u_table_global; /* - y_table = new unsigned char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - y_table[c] = 0; - else if ( c >= 235 ) - y_table[c] = 255; - else - y_table[c] = (255*(c-16))/219; - } + y_table = new unsigned char[256]; + for ( int i = 0; i <= 255; i++ ) + { + unsigned char c = i; + if ( c <= 16 ) + y_table[c] = 0; + else if ( c >= 235 ) + y_table[c] = 255; + else + y_table[c] = (255*(c-16))/219; + } - uv_table = new signed char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - uv_table[c] = -127; - else if ( c >= 240 ) - uv_table[c] = 127; - else - uv_table[c] = (127*(c-128))/112; - } + uv_table = new signed char[256]; + for ( int i = 0; i <= 255; i++ ) + { + unsigned char c = i; + if ( c <= 16 ) + uv_table[c] = -127; + else if ( c >= 240 ) + uv_table[c] = 127; + else + uv_table[c] = (127*(c-128))/112; + } - r_v_table = new short[255]; - g_v_table = new short[255]; - g_u_table = new short[255]; - b_u_table = new short[255]; - for ( int i = 0; i < 255; i++ ) - { - r_v_table[i] = (1402*(i-128))/1000; - g_u_table[i] = (344*(i-128))/1000; - g_v_table[i] = (714*(i-128))/1000; - b_u_table[i] = (1772*(i-128))/1000; - } + r_v_table = new short[255]; + g_v_table = new short[255]; + g_u_table = new short[255]; + b_u_table = new short[255]; + for ( int i = 0; i < 255; i++ ) + { + r_v_table[i] = (1402*(i-128))/1000; + g_u_table[i] = (344*(i-128))/1000; + g_v_table[i] = (714*(i-128))/1000; + b_u_table[i] = (1772*(i-128))/1000; + } */ - + initialised = true; } /* Requests a writeable buffer to the image. This is safer than buffer() because this way we can guarantee that a buffer of required size exists */ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) { unsigned int newsize; - + if(p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32) { Error("WriteBuffer called with unexpected colours: %d",p_colours); return NULL; } - + if(!p_height || !p_width) { Error("WriteBuffer called with invalid width or height: %d %d",p_width,p_height); return NULL; } - + if(p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder) { newsize = (p_width * p_height) * p_colours; - + if(buffer == NULL) { AllocImgBuffer(newsize); } else { @@ -375,7 +452,7 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei } } } - + width = p_width; height = p_height; colours = p_colours; @@ -383,9 +460,9 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei pixels = height*width; size = newsize; } - + return buffer; - + } /* Assign an existing buffer to the image instead of copying from a source buffer. The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. */ @@ -406,12 +483,12 @@ void Image::AssignDirect( const unsigned int p_width, const unsigned int p_heigh } unsigned int new_buffer_size = ((p_width*p_height)*p_colours); - + if(buffer_size < new_buffer_size) { Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d*%d colours = %zu",buffer_size, p_width, p_height, p_colours, new_buffer_size ); return; } - + if(holdbuffer && buffer) { if(new_buffer_size > allocation) { Error("Held buffer is undersized for assigned buffer"); @@ -423,55 +500,55 @@ void Image::AssignDirect( const unsigned int p_width, const unsigned int p_heigh subpixelorder = p_subpixelorder; pixels = height*width; size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - + /* Copy into the held buffer */ if(new_buffer != buffer) (*fptr_imgbufcpy)(buffer, new_buffer, size); - + /* Free the new buffer */ DumpBuffer(new_buffer, p_buffertype); } } else { /* Free an existing buffer if any */ DumpImgBuffer(); - + width = p_width; height = p_height; colours = p_colours; subpixelorder = p_subpixelorder; pixels = height*width; size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - + allocation = buffer_size; buffertype = p_buffertype; buffer = new_buffer; } - + } void Image::Assign(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size) { unsigned int new_size = (p_width * p_height) * p_colours; - + if(new_buffer == NULL) { Error("Attempt to assign buffer from a NULL pointer"); return; } - + if(buffer_size < new_size) { Error("Attempt to assign buffer from an undersized buffer of size: %zu",buffer_size); return; } - + if(!p_height || !p_width) { Error("Attempt to assign buffer with invalid width or height: %d %d",p_width,p_height); return; } - + if(p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32) { Error("Attempt to assign buffer with unexpected colours per pixel: %d",p_colours); return; } - + if ( !buffer || p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder) { if (holdbuffer && buffer) { @@ -485,7 +562,7 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons AllocImgBuffer(new_size); } } - + width = p_width; height = p_height; pixels = width*height; @@ -493,25 +570,25 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons subpixelorder = p_subpixelorder; size = new_size; } - + if(new_buffer != buffer) (*fptr_imgbufcpy)(buffer, new_buffer, size); - + } void Image::Assign( const Image &image ) { unsigned int new_size = (image.width * image.height) * image.colours; - + if(image.buffer == NULL) { Error("Attempt to assign image with an empty buffer"); return; } - + if(image.colours != ZM_COLOUR_GRAY8 && image.colours != ZM_COLOUR_RGB24 && image.colours != ZM_COLOUR_RGB32) { Error("Attempt to assign image with unexpected colours per pixel: %d",image.colours); return; } - + if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder) { if (holdbuffer && buffer) { @@ -525,7 +602,7 @@ void Image::Assign( const Image &image ) { AllocImgBuffer(new_size); } } - + width = image.width; height = image.height; pixels = width*height; @@ -533,7 +610,7 @@ void Image::Assign( const Image &image ) { subpixelorder = image.subpixelorder; size = new_size; } - + if(image.buffer != buffer) (*fptr_imgbufcpy)(buffer, image.buffer, size); } @@ -544,14 +621,14 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p { Panic( "Attempt to highlight image edges when colours = %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,p_subpixelorder); - + /* Create a new image of the target format */ Image *high_image = new Image( width, height, p_colours, p_subpixelorder ); uint8_t* high_buff = high_image->WriteBuffer(width, height, p_colours, p_subpixelorder); - + /* Set image to all black */ high_image->Clear(); @@ -559,7 +636,7 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p unsigned int lo_y = limits?limits->Lo().Y():0; unsigned int hi_x = limits?limits->Hi().X():width-1; unsigned int hi_y = limits?limits->Hi().Y():height-1; - + if ( p_colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) @@ -631,7 +708,7 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p } } } - + return( high_image ); } @@ -727,7 +804,7 @@ bool Image::ReadJpeg( const char *filename, unsigned int p_colours, unsigned int fclose( infile ); return( false ); } - + /* 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 */ if(cinfo->dc_huff_tbl_ptrs[0] == NULL) { @@ -741,67 +818,67 @@ bool Image::ReadJpeg( const char *filename, unsigned int p_colours, unsigned int { Debug(9,"Image dimensions differ. Old: %ux%u New: %ux%u",width,height,new_width,new_height); } - + switch(p_colours) { case ZM_COLOUR_GRAY8: - { - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; - } + { + cinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + cinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #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 - } + } case ZM_COLOUR_RGB24: default: - { - new_colours = ZM_COLOUR_RGB24; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + new_colours = ZM_COLOUR_RGB24; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + cinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - 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; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + 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; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; - } + */ + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; + } } - + if(WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == NULL) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress( cinfo ); @@ -873,97 +950,97 @@ bool Image::WriteJpeg( const char *filename, int quality_override, struct timeva cinfo->image_width = width; /* image width and height, in pixels */ cinfo->image_height = height; - + switch(colours) { case ZM_COLOUR_GRAY8: - { - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; - } + { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } + cinfo->input_components = 4; + if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); - jpeg_abort_compress( cinfo ); - fclose(outfile); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress( cinfo ); + fclose(outfile); + return(false); #endif - break; - } + break; + } case ZM_COLOUR_RGB24: default: - { - cinfo->input_components = 3; - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + cinfo->input_components = 3; + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; + 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); + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); + jpeg_abort_compress( cinfo ); + fclose(outfile); + return(false); #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->in_color_space = JCS_RGB; - } - break; - } + */ + cinfo->in_color_space = JCS_RGB; + } + break; + } } - + jpeg_set_defaults( cinfo ); jpeg_set_quality( cinfo, quality, FALSE ); cinfo->dct_method = JDCT_FASTEST; jpeg_start_compress( cinfo, TRUE ); - if ( config.add_jpeg_comments && text[0] ) - { + if ( config.add_jpeg_comments && text[0] ) { jpeg_write_marker( cinfo, JPEG_COM, (const JOCTET *)text, strlen(text) ); } - // 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 - // No timestamp just leave off the exif section. - if(timestamp.tv_sec) - { - #define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds - #define EXIFTIMES_MS_LEN 0x03 - #define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes - #define EXIFTIMES_LEN 0x13 // = 19 - #define EXIF_CODE 0xE1 + // 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 + // No timestamp just leave off the exif section. + if(timestamp.tv_sec) + { +#define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds +#define EXIFTIMES_MS_LEN 0x03 +#define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes +#define EXIFTIMES_LEN 0x13 // = 19 +#define EXIF_CODE 0xE1 - char timebuf[64], msbuf[64]; - strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); - snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it - unsigned char exiftimes[82] = { - 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, - 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, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00 }; - memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); - memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf ,EXIFTIMES_MS_LEN); - jpeg_write_marker (cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes) ); - } + // This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64]; + char timebuf[64], msbuf[64]; + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); + snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it + unsigned char exiftimes[82] = { + 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, + 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, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00 }; + memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); + memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN); + jpeg_write_marker( cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes) ); + } JSAMPROW row_pointer; /* pointer to a single row */ int row_stride = cinfo->image_width * colours; /* physical row width in buffer */ @@ -1010,7 +1087,7 @@ bool Image::DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int jpeg_abort_decompress( cinfo ); return( false ); } - + /* 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 */ if(cinfo->dc_huff_tbl_ptrs[0] == NULL) { @@ -1024,67 +1101,67 @@ bool Image::DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int { Debug(9,"Image dimensions differ. Old: %ux%u New: %ux%u",width,height,new_width,new_height); } - + switch(p_colours) { case ZM_COLOUR_GRAY8: - { - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; - } + { + cinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + cinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #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 - } + } case ZM_COLOUR_RGB24: default: - { - new_colours = ZM_COLOUR_RGB24; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + new_colours = ZM_COLOUR_RGB24; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + cinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - 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; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + 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; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; - } + */ + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; + } } - + if(WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == NULL) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress( cinfo ); @@ -1135,59 +1212,59 @@ bool Image::EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_over switch(colours) { case ZM_COLOUR_GRAY8: - { - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; - } + { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } + cinfo->input_components = 4; + if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); - jpeg_abort_compress( cinfo ); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress( cinfo ); + return(false); #endif - break; - } + break; + } case ZM_COLOUR_RGB24: default: - { - cinfo->input_components = 3; - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + cinfo->input_components = 3; + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; + cinfo->in_color_space = JCS_EXT_BGR; #else - Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); - jpeg_abort_compress( cinfo ); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); + jpeg_abort_compress( cinfo ); + return(false); #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->in_color_space = JCS_RGB; - } - break; - } + */ + cinfo->in_color_space = JCS_RGB; + } + break; + } } - + jpeg_set_defaults( cinfo ); jpeg_set_quality( cinfo, quality, FALSE ); cinfo->dct_method = JDCT_FASTEST; @@ -1260,7 +1337,7 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig unsigned int new_size = new_width*new_height*colours; uint8_t *new_buffer = AllocBuffer(new_size); - + unsigned int new_stride = new_width*colours; for ( unsigned int y = lo_y, ny = 0; y <= hi_y; y++, ny++ ) { @@ -1287,17 +1364,17 @@ void Image::Overlay( const Image &image ) { Panic( "Attempt to overlay different sized images, expected %dx%d, got %dx%d", width, height, image.width, image.height ); } - + if( colours == image.colours && subpixelorder != image.subpixelorder ) { Warning("Attempt to overlay images of same format but with different subpixel order."); } - + /* Grayscale ontop of grayscale - complete */ if ( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_GRAY8 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( *psrc ) @@ -1307,15 +1384,15 @@ void Image::Overlay( const Image &image ) pdest++; psrc++; } - - /* RGB24 ontop of grayscale - convert to same format first - complete */ + + /* RGB24 ontop of grayscale - convert to same format first - complete */ } else if ( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB24 ) { Colourise(image.colours, image.subpixelorder); - + const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) @@ -1327,15 +1404,15 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc += 3; } - - /* RGB32 ontop of grayscale - convert to same format first - complete */ + + /* RGB32 ontop of grayscale - convert to same format first - complete */ } else if( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB32 ) { Colourise(image.colours, image.subpixelorder); - + const Rgb* const max_ptr = (Rgb*)(buffer+size); const Rgb* prsrc = (Rgb*)image.buffer; Rgb* prdest = (Rgb*)buffer; - + if(subpixelorder == ZM_SUBPIX_ORDER_RGBA || subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGB\BGR\RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1357,13 +1434,13 @@ void Image::Overlay( const Image &image ) prsrc++; } } - - /* Grayscale ontop of RGB24 - complete */ + + /* Grayscale ontop of RGB24 - complete */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_GRAY8 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( *psrc ) @@ -1373,13 +1450,13 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc++; } - - /* RGB24 ontop of RGB24 - not complete. need to take care of different subpixel orders */ + + /* RGB24 ontop of RGB24 - not complete. need to take care of different subpixel orders */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_RGB24 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) @@ -1391,17 +1468,17 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc += 3; } - - /* RGB32 ontop of RGB24 - TO BE DONE */ + + /* RGB32 ontop of RGB24 - TO BE DONE */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_RGB32 ) { Error("Overlay of RGB32 ontop of RGB24 is not supported."); - - /* Grayscale ontop of RGB32 - complete */ + + /* Grayscale ontop of RGB32 - complete */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_GRAY8 ) { const Rgb* const max_ptr = (Rgb*)(buffer+size); Rgb* prdest = (Rgb*)buffer; const uint8_t* psrc = image.buffer; - + if(subpixelorder == ZM_SUBPIX_ORDER_RGBA || subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1423,17 +1500,17 @@ void Image::Overlay( const Image &image ) psrc++; } } - - /* RGB24 ontop of RGB32 - TO BE DONE */ + + /* RGB24 ontop of RGB32 - TO BE DONE */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_RGB24 ) { Error("Overlay of RGB24 ontop of RGB32 is not supported."); - - /* RGB32 ontop of RGB32 - not complete. need to take care of different subpixel orders */ + + /* RGB32 ontop of RGB32 - not complete. need to take care of different subpixel orders */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_RGB32 ) { const Rgb* const max_ptr = (Rgb*)(buffer+size); Rgb* prdest = (Rgb*)buffer; const Rgb* prsrc = (Rgb*)image.buffer; - + if(image.subpixelorder == ZM_SUBPIX_ORDER_RGBA || image.subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGB\BGR\RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1456,7 +1533,7 @@ void Image::Overlay( const Image &image ) } } } - + } /* RGB32 compatible: complete */ @@ -1521,7 +1598,7 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) } else { Error("Overlay called with unexpected colours: %d", colours); } - + } void Image::Blend( const Image &image, int transparency ) @@ -1532,33 +1609,33 @@ void Image::Blend( const Image &image, int transparency ) unsigned long milpixels; #endif uint8_t* new_buffer; - + if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to blend different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder ); } - + if(transparency <= 0) return; - + new_buffer = AllocBuffer(size); - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); #endif - + /* Do the blending */ (*fptr_blend)(buffer, image.buffer, new_buffer, size, transparency); - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); timespec_diff(&start,&end,&diff); - + executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)size)/((((long double)executetime)/1000)); Debug(5, "Blend: %u colours blended in %llu nanoseconds, %lu million colours/s\n",size,executetime,milpixels); #endif - + AssignDirect( width, height, colours, subpixelorder, new_buffer, size, ZM_BUFTYPE_ZM); } @@ -1650,6 +1727,8 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres unsigned int size = result->size; for ( unsigned int c = 0; c < colours; c++ ) { + unsigned int ref_colour_rgb = RGB_VAL(ref_colour,c); + for ( unsigned int i = 0; i < size; i++ ) { unsigned int count = 0; @@ -1658,11 +1737,9 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres { uint8_t *psrc = images[j]->buffer+c; -#ifndef SOLARIS - if ( (unsigned)abs((*psrc)-RGB_VAL(ref_colour,c)) >= RGB_VAL(threshold,c) ) -#else - if ( (unsigned)std::abs((*psrc)-RGB_VAL(ref_colour,c)) >= RGB_VAL(threshold,c) ) -#endif + unsigned int diff = ((*psrc)-ref_colour_rgb) > 0 ? (*psrc)-ref_colour_rgb : ref_colour_rgb - (*psrc); + + if (diff >= RGB_VAL(threshold,c)) { count++; } @@ -1683,63 +1760,63 @@ void Image::Delta( const Image &image, Image* targetimage) const unsigned long long executetime; unsigned long milpixels; #endif - + if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to get delta of different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder); } - + uint8_t *pdiff = targetimage->WriteBuffer(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); - + if(pdiff == NULL) { Panic("Failed requesting writeable buffer for storing the delta image"); } - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); #endif - + switch(colours) { case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - (*fptr_delta8_bgr)(buffer, image.buffer, pdiff, pixels); - } else { - /* Assume RGB subpixel order */ - (*fptr_delta8_rgb)(buffer, image.buffer, pdiff, pixels); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + (*fptr_delta8_bgr)(buffer, image.buffer, pdiff, pixels); + } else { + /* Assume RGB subpixel order */ + (*fptr_delta8_rgb)(buffer, image.buffer, pdiff, pixels); + } + break; + } case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - (*fptr_delta8_argb)(buffer, image.buffer, pdiff, pixels); - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - (*fptr_delta8_abgr)(buffer, image.buffer, pdiff, pixels); - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - (*fptr_delta8_bgra)(buffer, image.buffer, pdiff, pixels); - } else { - /* Assume RGBA subpixel order */ - (*fptr_delta8_rgba)(buffer, image.buffer, pdiff, pixels); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + (*fptr_delta8_argb)(buffer, image.buffer, pdiff, pixels); + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + (*fptr_delta8_abgr)(buffer, image.buffer, pdiff, pixels); + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + (*fptr_delta8_bgra)(buffer, image.buffer, pdiff, pixels); + } else { + /* Assume RGBA subpixel order */ + (*fptr_delta8_rgba)(buffer, image.buffer, pdiff, pixels); + } + break; + } case ZM_COLOUR_GRAY8: - (*fptr_delta8_gray8)(buffer, image.buffer, pdiff, pixels); - break; + (*fptr_delta8_gray8)(buffer, image.buffer, pdiff, pixels); + break; default: - Panic("Delta called with unexpected colours: %d",colours); - break; + Panic("Delta called with unexpected colours: %d",colours); + break; } - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); timespec_diff(&start,&end,&diff); - + executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)pixels)/((((long double)executetime)/1000)); Debug(5, "Delta: %u delta pixels generated in %llu nanoseconds, %lu million pixels/s\n",pixels,executetime,milpixels); @@ -1768,7 +1845,7 @@ const Coord Image::centreCoord( const char *text ) const line = text+index; line_no++; } - int x = (width - (max_line_len * CHAR_WIDTH) ) / 2; + int x = (width - (max_line_len * ZM_CHAR_WIDTH) ) / 2; int y = (height - (line_no * LINE_HEIGHT) ) / 2; return( Coord( x, y ) ); } @@ -1810,7 +1887,7 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour } } else if ( colours == ZM_COLOUR_RGB32 ) - { + { for ( unsigned int x = 0; x < width; x++, ptr += colours ) { Rgb *temp_ptr = (Rgb*)ptr; @@ -1818,10 +1895,10 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour *temp_ptr = pixel_rgb_col; i++; } - } else { - Panic("MaskPrivacy called with unexpected colours: %d", colours); - return; - } + } else { + Panic("MaskPrivacy called with unexpected colours: %d", colours); + return; + } } } @@ -1843,7 +1920,7 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int const uint8_t fg_bw_col = fg_colour & 0xff; const Rgb fg_rgb_col = rgb_convert(fg_colour,subpixelorder); const bool fg_trans = (fg_colour == RGB_TRANSPARENT); - + const uint8_t bg_r_col = RED_VAL_RGBA(bg_colour); const uint8_t bg_g_col = GREEN_VAL_RGBA(bg_colour); const uint8_t bg_b_col = BLUE_VAL_RGBA(bg_colour); @@ -1858,7 +1935,7 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int while ( (index < text_len) && (line_len = strcspn( line, "\n" )) ) { - unsigned int line_width = line_len * CHAR_WIDTH * size; + unsigned int line_width = line_len * ZM_CHAR_WIDTH * size; unsigned int lo_line_x = coord.X(); unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size); @@ -1889,17 +1966,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int if ( colours == ZM_COLOUR_GRAY8 ) { unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += width ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1919,17 +1996,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int unsigned int wc = width * colours; unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1951,42 +2028,42 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int } } else if ( colours == ZM_COLOUR_RGB32 ) - { + { unsigned int wc = width * colours; uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { Rgb* temp_ptr = (Rgb*)ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { if ( !fg_trans ) { - *temp_ptr = fg_rgb_col; + *temp_ptr = fg_rgb_col; } } else if ( !bg_trans ) { - *temp_ptr = bg_rgb_col; + *temp_ptr = bg_rgb_col; } } } } - - } else { - Panic("Annotate called with unexpected colours: %d",colours); - return; - } - + + } else { + Panic("Annotate called with unexpected colours: %d",colours); + return; + } + index += line_len; while ( text[index] == '\n' ) { @@ -1997,18 +2074,14 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int } } -void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) -{ +void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) { char time_text[64]; strftime( time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime( &when ) ); char text[64]; - if ( label ) - { + if ( label ) { snprintf( text, sizeof(text), "%s - %s", label, time_text ); Annotate( text, coord, size ); - } - else - { + } else { Annotate( time_text, coord, size ); } } @@ -2017,21 +2090,21 @@ void Image::Timestamp( const char *label, const time_t when, const Coord &coord, void Image::Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder) { Debug(9, "Colourise: Req colours: %u Req subpixel order: %u Current colours: %u Current subpixel order: %u",p_reqcolours,p_reqsubpixelorder,colours,subpixelorder); - + if ( colours != ZM_COLOUR_GRAY8) { Warning("Target image is already colourised, colours: %u",colours); return; } - + if ( p_reqcolours == ZM_COLOUR_RGB32 ) { /* RGB32 */ Rgb* new_buffer = (Rgb*)AllocBuffer(pixels*sizeof(Rgb)); - + const uint8_t *psrc = buffer; Rgb* pdest = new_buffer; Rgb subpixel; Rgb newpixel; - + if ( p_reqsubpixelorder == ZM_SUBPIX_ORDER_ABGR || p_reqsubpixelorder == ZM_SUBPIX_ORDER_ARGB) { /* ARGB\ABGR subpixel order. alpha byte is first (mem+0), so we need to shift the pixel left in the end */ for(unsigned int i=0;i= 35) { + /* Use SSSE3 functions */ switch(subpixelorder) { case ZM_SUBPIX_ORDER_BGRA: - std_convert_bgra_gray8(buffer,buffer,pixels); - break; + ssse3_convert_bgra_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_ARGB: - std_convert_argb_gray8(buffer,buffer,pixels); - break; + ssse3_convert_argb_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_ABGR: - std_convert_abgr_gray8(buffer,buffer,pixels); - break; + ssse3_convert_abgr_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_RGBA: default: - std_convert_rgba_gray8(buffer,buffer,pixels); - break; + ssse3_convert_rgba_gray8(buffer,buffer,pixels); + break; } } else { - /* Assume RGB24 */ - switch(subpixelorder) { - case ZM_SUBPIX_ORDER_BGR: - std_convert_bgr_gray8(buffer,buffer,pixels); - break; - case ZM_SUBPIX_ORDER_RGB: - default: - std_convert_rgb_gray8(buffer,buffer,pixels); - break; - } - + /* Use standard functions */ + if ( colours == ZM_COLOUR_RGB32 ) + { + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGRA: + std_convert_bgra_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ARGB: + std_convert_argb_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ABGR: + std_convert_abgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGBA: + default: + std_convert_rgba_gray8(buffer,buffer,pixels); + break; + } + } else { + /* Assume RGB24 */ + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGR: + std_convert_bgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGB: + default: + std_convert_rgb_gray8(buffer,buffer,pixels); + break; + } + } } } @@ -2120,10 +2212,10 @@ void Image::Fill( Rgb colour, const Box *limits ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - + unsigned int lo_x = limits?limits->Lo().X():0; unsigned int lo_y = limits?limits->Lo().Y():0; unsigned int hi_x = limits?limits->Hi().X():width-1; @@ -2157,7 +2249,7 @@ void Image::Fill( Rgb colour, const Box *limits ) for ( unsigned int y = lo_y; y <= (unsigned int)hi_y; y++ ) { Rgb *p = (Rgb*)&buffer[((y*width)+lo_x)<<2]; - + for ( unsigned int x = lo_x; x <= (unsigned int)hi_x; x++, p++) { /* Fast, copies the entire pixel in a single pass */ @@ -2173,12 +2265,12 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) /* Allow the faster version to be used if density is not used (density=1) */ if(density <= 1) return Fill(colour,limits); - + if ( !(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32 ) ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); @@ -2227,7 +2319,7 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) } } } - + } /* RGB32 compatible: complete */ @@ -2237,10 +2329,10 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { Panic( "Attempt to outline image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - + int n_coords = polygon.getNumCoords(); for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { @@ -2334,7 +2426,7 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) *(Rgb*)(buffer+(((int(round(y))*width)+x)<<2)) = colour; } } - + } } } @@ -2346,7 +2438,7 @@ void Image::Fill( Rgb colour, int density, const Polygon &polygon ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); @@ -2492,7 +2584,7 @@ void Image::Fill( Rgb colour, const Polygon &polygon ) /* RGB32 compatible: complete */ void Image::Rotate( int angle ) { - + angle %= 360; if ( !angle ) @@ -2503,7 +2595,7 @@ void Image::Rotate( int angle ) { return; } - + unsigned int new_height = height; unsigned int new_width = width; uint8_t* rotate_buffer = AllocBuffer(size); @@ -2511,157 +2603,157 @@ void Image::Rotate( int angle ) switch( angle ) { case 90 : - { - new_height = width; - new_width = height; + { + new_height = width; + new_width = height; - unsigned int line_bytes = new_width*colours; - unsigned char *s_ptr = buffer; + unsigned int line_bytes = new_width*colours; + unsigned char *s_ptr = buffer; - if ( colours == ZM_COLOUR_GRAY8 ) - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + if ( colours == ZM_COLOUR_GRAY8 ) { - d_ptr = rotate_buffer+(i-1); - for ( unsigned int j = new_height; j > 0; j-- ) + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_ptr = *s_ptr++; - d_ptr += line_bytes; + d_ptr = rotate_buffer+(i-1); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_ptr = *s_ptr++; + d_ptr += line_bytes; + } } } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else if ( colours == ZM_COLOUR_RGB32 ) { - d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); - for ( unsigned int j = new_height; j > 0; j-- ) + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_rptr = *s_rptr++; - d_rptr += new_width; + d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_rptr = *s_rptr++; + d_rptr += new_width; + } } } - } - else /* Assume RGB24 */ - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else /* Assume RGB24 */ { - d_ptr = rotate_buffer+((i-1)*3); - for ( unsigned int j = new_height; j > 0; j-- ) + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_ptr = *s_ptr++; - *(d_ptr+1) = *s_ptr++; - *(d_ptr+2) = *s_ptr++; - d_ptr += line_bytes; + d_ptr = rotate_buffer+((i-1)*3); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_ptr = *s_ptr++; + *(d_ptr+1) = *s_ptr++; + *(d_ptr+2) = *s_ptr++; + d_ptr += line_bytes; + } } } + break; } - break; - } case 180 : - { - unsigned char *s_ptr = buffer+size; - unsigned char *d_ptr = rotate_buffer; + { + unsigned char *s_ptr = buffer+size; + unsigned char *d_ptr = rotate_buffer; - if ( colours == ZM_COLOUR_GRAY8 ) - { - while( s_ptr > buffer ) + if ( colours == ZM_COLOUR_GRAY8 ) { - s_ptr--; - *d_ptr++ = *s_ptr; - } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr = (Rgb*)d_ptr; - while( s_rptr > (Rgb*)buffer ) - { - s_rptr--; - *d_rptr++ = *s_rptr; - } - } - else /* Assume RGB24 */ - { - while( s_ptr > buffer ) - { - s_ptr -= 3; - *d_ptr++ = *s_ptr; - *d_ptr++ = *(s_ptr+1); - *d_ptr++ = *(s_ptr+2); - } - } - break; - } - case 270 : - { - new_height = width; - new_width = height; - - unsigned int line_bytes = new_width*colours; - unsigned char *s_ptr = buffer+size; - - if ( colours == ZM_COLOUR_GRAY8 ) - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) - { - d_ptr = rotate_buffer+(i-1); - for ( unsigned int j = new_height; j > 0; j-- ) + while( s_ptr > buffer ) { s_ptr--; - *d_ptr = *s_ptr; - d_ptr += line_bytes; + *d_ptr++ = *s_ptr; } } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else if ( colours == ZM_COLOUR_RGB32 ) { - d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); - for ( unsigned int j = new_height; j > 0; j-- ) + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr = (Rgb*)d_ptr; + while( s_rptr > (Rgb*)buffer ) { s_rptr--; - *d_rptr = *s_rptr; - d_rptr += new_width; + *d_rptr++ = *s_rptr; } } - } - else /* Assume RGB24 */ - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else /* Assume RGB24 */ { - d_ptr = rotate_buffer+((i-1)*3); - for ( unsigned int j = new_height; j > 0; j-- ) + while( s_ptr > buffer ) { - *(d_ptr+2) = *(--s_ptr); - *(d_ptr+1) = *(--s_ptr); - *d_ptr = *(--s_ptr); - d_ptr += line_bytes; + s_ptr -= 3; + *d_ptr++ = *s_ptr; + *d_ptr++ = *(s_ptr+1); + *d_ptr++ = *(s_ptr+2); } } + break; + } + case 270 : + { + new_height = width; + new_width = height; + + unsigned int line_bytes = new_width*colours; + unsigned char *s_ptr = buffer+size; + + if ( colours == ZM_COLOUR_GRAY8 ) + { + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_ptr = rotate_buffer+(i-1); + for ( unsigned int j = new_height; j > 0; j-- ) + { + s_ptr--; + *d_ptr = *s_ptr; + d_ptr += line_bytes; + } + } + } + else if ( colours == ZM_COLOUR_RGB32 ) + { + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); + for ( unsigned int j = new_height; j > 0; j-- ) + { + s_rptr--; + *d_rptr = *s_rptr; + d_rptr += new_width; + } + } + } + else /* Assume RGB24 */ + { + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_ptr = rotate_buffer+((i-1)*3); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *(d_ptr+2) = *(--s_ptr); + *(d_ptr+1) = *(--s_ptr); + *d_ptr = *(--s_ptr); + d_ptr += line_bytes; + } + } + } + break; } - break; - } } - + AssignDirect( new_width, new_height, colours, subpixelorder, rotate_buffer, size, ZM_BUFTYPE_ZM); - + } /* RGB32 compatible: complete */ void Image::Flip( bool leftright ) { uint8_t* flip_buffer = AllocBuffer(size); - + unsigned int line_bytes = width*colours; unsigned int line_bytes2 = 2*line_bytes; if ( leftright ) @@ -2726,9 +2818,9 @@ void Image::Flip( bool leftright ) d_ptr += line_bytes; } } - + AssignDirect( width, height, colours, subpixelorder, flip_buffer, size, ZM_BUFTYPE_ZM); - + } void Image::Scale( unsigned int factor ) @@ -2745,11 +2837,11 @@ void Image::Scale( unsigned int factor ) unsigned int new_width = (width*factor)/ZM_SCALE_BASE; unsigned int new_height = (height*factor)/ZM_SCALE_BASE; - + size_t scale_buffer_size = (new_width+1) * (new_height+1) * colours; - + uint8_t* scale_buffer = AllocBuffer(scale_buffer_size); - + if ( factor > ZM_SCALE_BASE ) { unsigned char *pd = scale_buffer; @@ -2816,7 +2908,7 @@ void Image::Scale( unsigned int factor ) { w_count += factor; w_index = w_count/ZM_SCALE_BASE; - + if ( w_index > last_w_index ) { for ( unsigned int c = 0; c < colours; c++ ) @@ -2836,15 +2928,15 @@ void Image::Scale( unsigned int factor ) new_width = last_w_index; new_height = last_h_index; } - + AssignDirect( new_width, new_height, colours, subpixelorder, scale_buffer, scale_buffer_size, ZM_BUFTYPE_ZM); - + } void Image::Deinterlace_Discard() { /* Simple deinterlacing. Copy the even lines into the odd lines */ - + if ( colours == ZM_COLOUR_GRAY8 ) { const uint8_t *psrc; @@ -2888,16 +2980,16 @@ void Image::Deinterlace_Discard() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Linear() { /* Simple deinterlacing. The odd lines are average of the line above and line below */ - + const uint8_t *pbelow, *pabove; uint8_t *pcurrent; - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)(height-1); y += 2) @@ -2964,15 +3056,15 @@ void Image::Deinterlace_Linear() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Blend() { /* Simple deinterlacing. Blend the fields together. 50% blend */ - + uint8_t *pabove, *pcurrent; - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)height; y += 2) @@ -3021,7 +3113,7 @@ void Image::Deinterlace_Blend() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Blend_CustomRatio(int divider) @@ -3031,14 +3123,14 @@ void Image::Deinterlace_Blend_CustomRatio(int divider) /* 2 = 25% blending */ /* 3 = 12.% blending */ /* 4 = 6.25% blending */ - + uint8_t *pabove, *pcurrent; uint8_t subpix1, subpix2; - + if ( divider < 1 || divider > 4 ) { Error("Deinterlace called with invalid blend ratio"); } - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)height; y += 2) @@ -3103,7 +3195,7 @@ void Image::Deinterlace_Blend_CustomRatio(int divider) } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } @@ -3113,44 +3205,44 @@ void Image::Deinterlace_4Field(const Image* next_image, unsigned int threshold) { Panic( "Attempt to deinterlace different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, next_image->width, next_image->height, next_image->colours, next_image->subpixelorder); } - + switch(colours) { case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - std_deinterlace_4field_bgr(buffer, next_image->buffer, threshold, width, height); - } else { - /* Assume RGB subpixel order */ - std_deinterlace_4field_rgb(buffer, next_image->buffer, threshold, width, height); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + std_deinterlace_4field_bgr(buffer, next_image->buffer, threshold, width, height); + } else { + /* Assume RGB subpixel order */ + std_deinterlace_4field_rgb(buffer, next_image->buffer, threshold, width, height); + } + break; + } case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - (*fptr_deinterlace_4field_argb)(buffer, next_image->buffer, threshold, width, height); - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - (*fptr_deinterlace_4field_abgr)(buffer, next_image->buffer, threshold, width, height); - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - (*fptr_deinterlace_4field_bgra)(buffer, next_image->buffer, threshold, width, height); - } else { - /* Assume RGBA subpixel order */ - (*fptr_deinterlace_4field_rgba)(buffer, next_image->buffer, threshold, width, height); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + (*fptr_deinterlace_4field_argb)(buffer, next_image->buffer, threshold, width, height); + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + (*fptr_deinterlace_4field_abgr)(buffer, next_image->buffer, threshold, width, height); + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + (*fptr_deinterlace_4field_bgra)(buffer, next_image->buffer, threshold, width, height); + } else { + /* Assume RGBA subpixel order */ + (*fptr_deinterlace_4field_rgba)(buffer, next_image->buffer, threshold, width, height); + } + break; + } case ZM_COLOUR_GRAY8: - (*fptr_deinterlace_4field_gray8)(buffer, next_image->buffer, threshold, width, height); - break; + (*fptr_deinterlace_4field_gray8)(buffer, next_image->buffer, threshold, width, height); + break; default: - Panic("Deinterlace_4Field called with unexpected colours: %d",colours); - break; + Panic("Deinterlace_4Field called with unexpected colours: %d",colours); + break; } - + } @@ -3165,7 +3257,7 @@ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, u static uint32_t divider = 0; static uint32_t clearmask = 0; static double current_blendpercent = 0.0; - + if(current_blendpercent != blendpercent) { /* Attempt to match the blending percent to one of the possible values */ if(blendpercent < 2.34375) { @@ -3197,29 +3289,29 @@ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, u } __asm__ __volatile__( - "movd %4, %%xmm3\n\t" - "movd %5, %%xmm4\n\t" - "pshufd $0x0, %%xmm3, %%xmm3\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x10, %2\n\t" - "sse2_fastblend_iter:\n\t" - "movdqa (%0,%3),%%xmm0\n\t" - "movdqa %%xmm0,%%xmm2\n\t" - "movdqa (%1,%3),%%xmm1\n\t" - "psrlq %%xmm4,%%xmm0\n\t" - "psrlq %%xmm4,%%xmm1\n\t" - "pand %%xmm3,%%xmm1\n\t" - "pand %%xmm3,%%xmm0\n\t" - "psubb %%xmm0,%%xmm1\n\t" - "paddb %%xmm2,%%xmm1\n\t" - "movntdq %%xmm1,(%2,%3)\n\t" - "sub $0x10, %3\n\t" - "jnz sse2_fastblend_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (clearmask), "m" (divider) - : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" - ); + "movd %4, %%xmm3\n\t" + "movd %5, %%xmm4\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x10, %2\n\t" + "sse2_fastblend_iter:\n\t" + "movdqa (%0,%3),%%xmm0\n\t" + "movdqa %%xmm0,%%xmm2\n\t" + "movdqa (%1,%3),%%xmm1\n\t" + "psrlq %%xmm4,%%xmm0\n\t" + "psrlq %%xmm4,%%xmm1\n\t" + "pand %%xmm3,%%xmm1\n\t" + "pand %%xmm3,%%xmm0\n\t" + "psubb %%xmm0,%%xmm1\n\t" + "paddb %%xmm2,%%xmm1\n\t" + "movntdq %%xmm1,(%2,%3)\n\t" + "sub $0x10, %3\n\t" + "jnz sse2_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (clearmask), "m" (divider) + : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3229,7 +3321,7 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* static int divider = 0; static double current_blendpercent = 0.0; const uint8_t* const max_ptr = result + count; - + if(current_blendpercent != blendpercent) { /* Attempt to match the blending percent to one of the possible values */ if(blendpercent < 2.34375) { @@ -3253,7 +3345,7 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* } current_blendpercent = blendpercent; } - + while(result < max_ptr) { result[0] = ((col2[0] - col1[0])>>divider) + col1[0]; @@ -3272,21 +3364,190 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* result[13] = ((col2[13] - col1[13])>>divider) + col1[13]; result[14] = ((col2[14] - col1[14])>>divider) + col1[14]; result[15] = ((col2[15] - col1[15])>>divider) + col1[15]; - + col1 += 16; col2 += 16; result += 16; } } +/* FastBlend Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + static int8_t divider = 0; + static double current_blendpercent = 0.0; + + if(current_blendpercent != blendpercent) { + /* Attempt to match the blending percent to one of the possible values */ + if(blendpercent < 2.34375) { + // 1.5625% blending + divider = 6; + } else if(blendpercent >= 2.34375 && blendpercent < 4.6875) { + // 3.125% blending + divider = 5; + } else if(blendpercent >= 4.6875 && blendpercent < 9.375) { + // 6.25% blending + divider = 4; + } else if(blendpercent >= 9.375 && blendpercent < 18.75) { + // 12.5% blending + divider = 3; + } else if(blendpercent >= 18.75 && blendpercent < 37.5) { + // 25% blending + divider = 2; + } else if(blendpercent >= 37.5) { + // 50% blending + divider = 1; + } + // We only have instruction to shift left by a variable, going negative shifts right :) + divider *= -1; + current_blendpercent = blendpercent; + } + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + /* Q8(D16,D17) = col1tmp+0 */ + /* Q9(D18,D19) = col1tmp+16 */ + /* Q10(D20,D21) = col1tmp+32 */ + /* Q11(D22,D23) = col1tmp+48 */ + /* Q12(D24,D25) = divider */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.8 q12, r12\n\t" + "neon32_armv7_fastblend_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #256]\n\t" + "pld [%1, #256]\n\t" + "vrshl.u8 q8, q0, q12\n\t" + "vrshl.u8 q9, q1, q12\n\t" + "vrshl.u8 q10, q2, q12\n\t" + "vrshl.u8 q11, q3, q12\n\t" + "vrshl.u8 q4, q4, q12\n\t" + "vrshl.u8 q5, q5, q12\n\t" + "vrshl.u8 q6, q6, q12\n\t" + "vrshl.u8 q7, q7, q12\n\t" + "vsub.i8 q4, q4, q8\n\t" + "vsub.i8 q5, q5, q9\n\t" + "vsub.i8 q6, q6, q10\n\t" + "vsub.i8 q7, q7, q11\n\t" + "vadd.i8 q4, q4, q0\n\t" + "vadd.i8 q5, q5, q1\n\t" + "vadd.i8 q6, q6, q2\n\t" + "vadd.i8 q7, q7, q3\n\t" + "vstm %2!, {q4,q5,q6,q7}\n\t" + "subs %3, %3, #64\n\t" + "bne neon32_armv7_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider) + : "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "%q9", "%q10", "%q11", "%q12", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +__attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + static int8_t divider = 0; + static double current_blendpercent = 0.0; + + if(current_blendpercent != blendpercent) { + /* Attempt to match the blending percent to one of the possible values */ + if(blendpercent < 2.34375) { + // 1.5625% blending + divider = 6; + } else if(blendpercent >= 2.34375 && blendpercent < 4.6875) { + // 3.125% blending + divider = 5; + } else if(blendpercent >= 4.6875 && blendpercent < 9.375) { + // 6.25% blending + divider = 4; + } else if(blendpercent >= 9.375 && blendpercent < 18.75) { + // 12.5% blending + divider = 3; + } else if(blendpercent >= 18.75 && blendpercent < 37.5) { + // 25% blending + divider = 2; + } else if(blendpercent >= 37.5) { + // 50% blending + divider = 1; + } + // We only have instruction to shift left by a variable, going negative shifts right :) + divider *= -1; + current_blendpercent = blendpercent; + } + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + /* V24 = col1tmp+0 */ + /* V25 = col1tmp+16 */ + /* V26 = col1tmp+32 */ + /* V27 = col1tmp+48 */ + /* V28 = divider */ + + __asm__ __volatile__ ( + "mov x12, %4\n\t" + "dup v28.16b, w12\n\t" + "neon64_armv8_fastblend_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #256]\n\t" + "prfm pldl1keep, [%1, #256]\n\t" + "urshl v24.16b, v16.16b, v28.16b\n\t" + "urshl v25.16b, v17.16b, v28.16b\n\t" + "urshl v26.16b, v18.16b, v28.16b\n\t" + "urshl v27.16b, v19.16b, v28.16b\n\t" + "urshl v20.16b, v20.16b, v28.16b\n\t" + "urshl v21.16b, v21.16b, v28.16b\n\t" + "urshl v22.16b, v22.16b, v28.16b\n\t" + "urshl v23.16b, v23.16b, v28.16b\n\t" + "sub v20.16b, v20.16b, v24.16b\n\t" + "sub v21.16b, v21.16b, v25.16b\n\t" + "sub v22.16b, v22.16b, v26.16b\n\t" + "sub v23.16b, v23.16b, v27.16b\n\t" + "add v20.16b, v20.16b, v16.16b\n\t" + "add v21.16b, v21.16b, v17.16b\n\t" + "add v22.16b, v22.16b, v18.16b\n\t" + "add v23.16b, v23.16b, v19.16b\n\t" + "stp q20, q21, [%2], #32\n\t" + "stp q22, q23, [%2], #32\n\t" + "subs %3, %3, #64\n\t" + "bne neon64_armv8_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider) + : "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "%v25", "%v26", "%v27", "%v28", "cc", "memory" +); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { double divide = blendpercent / 100.0; double opacity = 1.0 - divide; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { *result++ = (*col1++ * opacity) + (*col2++ * divide); - + } } @@ -3296,7 +3557,7 @@ __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col __attribute__((noinline)) void std_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { /* Loop unrolling is used to work on 16 bytes (16 grayscale pixels) at a time */ const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { result[0] = abs(col1[0] - col2[0]); result[1] = abs(col1[1] - col2[1]); @@ -3314,7 +3575,7 @@ __attribute__((noinline)) void std_delta8_gray8(const uint8_t* col1, const uint8 result[13] = abs(col1[13] - col2[13]); result[14] = abs(col1[14] - col2[14]); result[15] = abs(col1[15] - col2[15]); - + col1 += 16; col2 += 16; result += 16; @@ -3326,7 +3587,7 @@ __attribute__((noinline)) void std_delta8_rgb(const uint8_t* col1, const uint8_t /* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3344,7 +3605,7 @@ __attribute__((noinline)) void std_delta8_rgb(const uint8_t* col1, const uint8_t g = abs(col1[10] - col2[10]); b = abs(col1[11] - col2[11]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; col2 += 12; result += 4; @@ -3356,7 +3617,7 @@ __attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t /* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3374,7 +3635,7 @@ __attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t g = abs(col1[10] - col2[10]); r = abs(col1[11] - col2[11]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; col2 += 12; result += 4; @@ -3386,7 +3647,7 @@ __attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3404,7 +3665,7 @@ __attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_ g = abs(col1[13] - col2[13]); b = abs(col1[14] - col2[14]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3416,7 +3677,7 @@ __attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3434,7 +3695,7 @@ __attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_ g = abs(col1[13] - col2[13]); r = abs(col1[14] - col2[14]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3446,7 +3707,7 @@ __attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); @@ -3464,7 +3725,7 @@ __attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_ g = abs(col1[14] - col2[14]); b = abs(col1[15] - col2[15]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3476,7 +3737,7 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); @@ -3494,13 +3755,242 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ g = abs(col1[14] - col2[14]); r = abs(col1[15] - col2[15]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; } } +/* Grayscale Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + + __asm__ __volatile__ ( + "neon32_armv7_delta8_gray8_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #512]\n\t" + "pld [%1, #512]\n\t" + "vabd.u8 q0, q0, q4\n\t" + "vabd.u8 q1, q1, q5\n\t" + "vabd.u8 q2, q2, q6\n\t" + "vabd.u8 q3, q3, q7\n\t" + "vstm %2!, {q0,q1,q2,q3}\n\t" + "subs %3, %3, #64\n\t" + "bne neon32_armv7_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* Grayscale Neon for AArch64 */ +__attribute__((noinline)) void neon64_armv8_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + + __asm__ __volatile__ ( + "neon64_armv8_delta8_gray8_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #512]\n\t" + "prfm pldl1keep, [%1, #512]\n\t" + "uabd v16.16b, v16.16b, v20.16b\n\t" + "uabd v17.16b, v17.16b, v21.16b\n\t" + "uabd v18.16b, v18.16b, v22.16b\n\t" + "uabd v19.16b, v19.16b, v23.16b\n\t" + "stp q16, q17, [%2], #32\n\t" + "stp q18, q19, [%2], #32\n\t" + "subs %3, %3, #64\n\t" + "bne neon64_armv8_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32 Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + /* Q8(D16,D17) = multiplier */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.32 q8, r12\n\t" + "neon32_armv7_delta8_rgb32_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #256]\n\t" + "pld [%1, #256]\n\t" + "vabd.u8 q0, q0, q4\n\t" + "vabd.u8 q1, q1, q5\n\t" + "vabd.u8 q2, q2, q6\n\t" + "vabd.u8 q3, q3, q7\n\t" + "vrshr.u8 q0, q0, #3\n\t" + "vrshr.u8 q1, q1, #3\n\t" + "vrshr.u8 q2, q2, #3\n\t" + "vrshr.u8 q3, q3, #3\n\t" + "vmul.i8 q0, q0, q8\n\t" + "vmul.i8 q1, q1, q8\n\t" + "vmul.i8 q2, q2, q8\n\t" + "vmul.i8 q3, q3, q8\n\t" + "vpadd.i8 d0, d0, d1\n\t" + "vpadd.i8 d2, d2, d3\n\t" + "vpadd.i8 d4, d4, d5\n\t" + "vpadd.i8 d6, d6, d7\n\t" + "vpadd.i8 d0, d0, d0\n\t" + "vpadd.i8 d1, d2, d2\n\t" + "vpadd.i8 d2, d4, d4\n\t" + "vpadd.i8 d3, d6, d6\n\t" + "vst4.32 {d0[0],d1[0],d2[0],d3[0]}, [%2]!\n\t" + "subs %3, %3, #16\n\t" + "bne neon32_armv7_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier) + : "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32 Neon for AArch64 */ +__attribute__((noinline)) void neon64_armv8_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + /* V24 = multiplier */ + + __asm__ __volatile__ ( + "mov x12, %4\n\t" + "dup v24.4s, w12\n\t" + "neon64_armv8_delta8_rgb32_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #256]\n\t" + "prfm pldl1keep, [%1, #256]\n\t" + "uabd v16.16b, v16.16b, v20.16b\n\t" + "uabd v17.16b, v17.16b, v21.16b\n\t" + "uabd v18.16b, v18.16b, v22.16b\n\t" + "uabd v19.16b, v19.16b, v23.16b\n\t" + "urshr v16.16b, v16.16b, #3\n\t" + "urshr v17.16b, v17.16b, #3\n\t" + "urshr v18.16b, v18.16b, #3\n\t" + "urshr v19.16b, v19.16b, #3\n\t" + "mul v16.16b, v16.16b, v24.16b\n\t" + "mul v17.16b, v17.16b, v24.16b\n\t" + "mul v18.16b, v18.16b, v24.16b\n\t" + "mul v19.16b, v19.16b, v24.16b\n\t" + "addp v16.16b, v16.16b, v16.16b\n\t" + "addp v17.16b, v17.16b, v17.16b\n\t" + "addp v18.16b, v18.16b, v18.16b\n\t" + "addp v19.16b, v19.16b, v19.16b\n\t" + "addp v16.16b, v16.16b, v16.16b\n\t" + "addp v17.16b, v17.16b, v17.16b\n\t" + "addp v18.16b, v18.16b, v18.16b\n\t" + "addp v19.16b, v19.16b, v19.16b\n\t" + "st4 {v16.s, v17.s, v18.s, v19.s}[0], [%2], #16\n\t" + "subs %3, %3, #16\n\t" + "bne neon64_armv8_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier) + : "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32: RGBA Neon for AArch32 */ +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + +/* RGB32: BGRA Neon for AArch32 */ +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00020501); +} + +/* RGB32: ARGB Neon for AArch32 */ +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x01050200); +} + +/* RGB32: ABGR Neon for AArch32 */ +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x02050100); +} + +/* RGB32: RGBA Neon for AArch64 */ +void neon64_armv8_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + +/* RGB32: BGRA Neon for AArch64 */ +void neon64_armv8_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00020501); +} + +/* RGB32: ARGB Neon for AArch64 */ +void neon64_armv8_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x01050200); +} + +/* RGB32: ABGR Neon for AArch64 */ +void neon64_armv8_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x02050100); +} + /* Grayscale SSE2 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("sse2"))) @@ -3509,24 +3999,24 @@ void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) __asm__ __volatile__ ( - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x10, %2\n\t" - "sse2_delta8_gray8_iter:\n\t" - "movdqa (%0,%3), %%xmm1\n\t" - "movdqa (%1,%3), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm3\n\t" - "movdqa %%xmm2, %%xmm4\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm3, %%xmm4\n\t" - "psubb %%xmm4, %%xmm2\n\t" - "movntdq %%xmm2, (%2,%3)\n\t" - "sub $0x10, %3\n\t" - "jnz sse2_delta8_gray8_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" - ); + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x10, %2\n\t" + "sse2_delta8_gray8_iter:\n\t" + "movdqa (%0,%3), %%xmm1\n\t" + "movdqa (%1,%3), %%xmm2\n\t" + "movdqa %%xmm1, %%xmm3\n\t" + "movdqa %%xmm2, %%xmm4\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm3, %%xmm4\n\t" + "psubb %%xmm4, %%xmm2\n\t" + "movntdq %%xmm2, (%2,%3)\n\t" + "sub $0x10, %3\n\t" + "jnz sse2_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3538,53 +4028,53 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_rgba_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_rgba_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_rgba_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x8, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm1, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_rgba_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3596,53 +4086,53 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_bgra_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_bgra_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_bgra_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x8, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_bgra_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3654,54 +4144,54 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_argb_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_argb_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_argb_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "psrld $0x8, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm1, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x18, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_argb_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3713,279 +4203,126 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_abgr_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_abgr_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_abgr_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "psrld $0x8, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x18, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_abgr_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); +#else + Panic("SSE function called on a non x86\\x86-64 platform"); +#endif +} + +/* RGB32 SSSE3 */ +#if defined(__i386__) || defined(__x86_64__) +__attribute__((noinline,__target__("ssse3"))) +#endif +void ssse3_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) + + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM2 - col2 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + + __asm__ __volatile__ ( + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov %4, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "ssse3_delta8_rgb32_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "psubb %%xmm2, %%xmm1\n\t" + "pabsb %%xmm1, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz ssse3_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } /* RGB32: RGBA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_rgba_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_rgba_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x00010502); } /* RGB32: BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_bgra_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_bgra_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x00020501); } /* RGB32: ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_argb_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_argb_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x01050200); } /* RGB32: ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_abgr_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_abgr_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x02050100); } @@ -3995,7 +4332,7 @@ void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[0]; g = col1[1]; @@ -4013,7 +4350,7 @@ __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_ g = col1[10]; b = col1[11]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; result += 4; } @@ -4023,7 +4360,7 @@ __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[0]; g = col1[1]; @@ -4041,7 +4378,7 @@ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_ g = col1[10]; r = col1[11]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; result += 4; } @@ -4051,7 +4388,7 @@ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[0]; g = col1[1]; @@ -4069,7 +4406,7 @@ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8 g = col1[13]; b = col1[14]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4079,7 +4416,7 @@ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[0]; g = col1[1]; @@ -4097,7 +4434,7 @@ __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8 g = col1[13]; r = col1[14]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4107,7 +4444,7 @@ __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[1]; g = col1[2]; @@ -4125,7 +4462,7 @@ __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8 g = col1[14]; b = col1[15]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4135,7 +4472,7 @@ __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[1]; g = col1[2]; @@ -4153,7 +4490,7 @@ __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8 g = col1[14]; r = col1[15]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4163,7 +4500,7 @@ __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { const uint16_t* yuvbuf = (const uint16_t*)col1; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { result[0] = (uint8_t)yuvbuf[0]; result[1] = (uint8_t)yuvbuf[1]; @@ -4181,61 +4518,74 @@ __attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8 result[13] = (uint8_t)yuvbuf[13]; result[14] = (uint8_t)yuvbuf[14]; result[15] = (uint8_t)yuvbuf[15]; - + yuvbuf += 16; result += 16; } } -/* RGBA to grayscale SSSE3 */ +/* RGB32 to grayscale SSSE3 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) #endif -void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { +void ssse3_convert_rgb32_gray8(const uint8_t* col1, uint8_t* result, unsigned long count, uint32_t multiplier) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %3, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x4, %1\n\t" - "ssse3_convert_rgba_gray8_iter:\n\t" - "movdqa (%0,%2,4), %%xmm3\n\t" - "psrlq $0x3, %%xmm3\n\t" - "pand %%xmm4, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%1,%2)\n\t" - "sub $0x4, %2\n\t" - "jnz ssse3_convert_rgba_gray8_iter\n\t" - : - : "r" (col1), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov %3, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x4, %1\n\t" + "ssse3_convert_rgb32_gray8_iter:\n\t" + "movdqa (%0,%2,4), %%xmm1\n\t" + "psrlq $0x3, %%xmm1\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%1,%2)\n\t" + "sub $0x4, %2\n\t" + "jnz ssse3_convert_rgb32_gray8_iter\n\t" + : + : "r" (col1), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } +/* RGBA to grayscale SSSE3 */ +void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00010502); +} + +/* BGRA to grayscale SSSE3 */ +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00020501); +} + +/* ARGB to grayscale SSSE3 */ +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x01050200); +} + +/* ABGR to grayscale SSSE3 */ +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x02050100); +} + /* Converts a YUYV image into grayscale by extracting the Y channel */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) @@ -4243,10 +4593,10 @@ __attribute__((noinline,__target__("ssse3"))) void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) unsigned long i = 0; - + __attribute__((aligned(16))) static const uint8_t movemask1[16] = {0,2,4,6,8,10,12,14,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; __attribute__((aligned(16))) static const uint8_t movemask2[16] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0,2,4,6,8,10,12,14}; - + /* XMM0 - General purpose */ /* XMM1 - General purpose */ /* XMM2 - unused */ @@ -4257,28 +4607,28 @@ void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned lon /* XMM7 - unused */ __asm__ __volatile__ ( - "movdqa %4, %%xmm3\n\t" - "movdqa %5, %%xmm4\n\t" - "algo_ssse3_convert_yuyv_gray8:\n\t" - "movdqa (%0), %%xmm0\n\t" - "pshufb %%xmm3, %%xmm0\n\t" - "movdqa 0x10(%0), %%xmm1\n\t" - "pshufb %%xmm4, %%xmm1\n\t" - "por %%xmm1, %%xmm0\n\t" - "movntdq %%xmm0, (%1)\n\t" - "add $0x10, %3\n\t" - "add $0x10, %1\n\t" - "add $0x20, %0\n\t" - "cmp %2, %3\n\t" - "jb algo_ssse3_convert_yuyv_gray8\n\t" - : + "movdqa %4, %%xmm3\n\t" + "movdqa %5, %%xmm4\n\t" + "algo_ssse3_convert_yuyv_gray8:\n\t" + "movdqa (%0), %%xmm0\n\t" + "pshufb %%xmm3, %%xmm0\n\t" + "movdqa 0x10(%0), %%xmm1\n\t" + "pshufb %%xmm4, %%xmm1\n\t" + "por %%xmm1, %%xmm0\n\t" + "movntdq %%xmm0, (%1)\n\t" + "add $0x10, %3\n\t" + "add $0x10, %1\n\t" + "add $0x20, %0\n\t" + "cmp %2, %3\n\t" + "jb algo_ssse3_convert_yuyv_gray8\n\t" + : #if (defined(_DEBUG) && !defined(__x86_64__)) /* Use one less register to allow compilation to success on 32bit with omit frame pointer disabled */ - : "r" (col1), "r" (result), "m" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) + : "r" (col1), "r" (result), "m" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) #else - : "r" (col1), "r" (result), "r" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) + : "r" (col1), "r" (result), "r" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) #endif - : "%xmm3", "%xmm4", "cc", "memory" - ); + : "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -4297,11 +4647,11 @@ __attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* r = y1 + r_v_table[v]; g = y1 - (g_u_table[u]+g_v_table[v]); b = y1 + b_u_table[u]; - + result[0] = r<0?0:(r>255?255:r); result[1] = g<0?0:(g>255?255:g); result[2] = b<0?0:(b>255?255:b); - + r = y2 + r_v_table[v]; g = y2 - (g_u_table[u]+g_v_table[v]); b = y2 + b_u_table[u]; @@ -4310,7 +4660,7 @@ __attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result[4] = g<0?0:(g>255?255:g); result[5] = b<0?0:(b>255?255:b); } - + } /* YUYV to RGBA - modified the one above */ @@ -4326,11 +4676,11 @@ __attribute__((noinline)) void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t r = y1 + r_v_table[v]; g = y1 - (g_u_table[u]+g_v_table[v]); b = y1 + b_u_table[u]; - + result[0] = r<0?0:(r>255?255:r); result[1] = g<0?0:(g>255?255:g); result[2] = b<0?0:(b>255?255:b); - + r = y2 + r_v_table[v]; g = y2 - (g_u_table[u]+g_v_table[v]); b = y2 + b_u_table[u]; @@ -4339,7 +4689,7 @@ __attribute__((noinline)) void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t result[5] = g<0?0:(g>255?255:g); result[6] = b<0?0:(b>255?255:b); } - + } /* RGB555 to RGB24 - relocated from zm_local_camera.cpp */ @@ -4426,9 +4776,9 @@ __attribute__((noinline)) void std_deinterlace_4field_gray8(uint8_t* col1, uint8 pabove += width; pnabove += width; pbelow += width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + width; while(pcurrent < max_ptr2) { @@ -4485,29 +4835,29 @@ __attribute__((noinline)) void std_deinterlace_4field_rgb(uint8_t* col1, uint8_t pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - b = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - b = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 3; - pnabove += 3; - pcurrent += 3; - pncurrent += 3; + r = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + b = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + b = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 3; + pnabove += 3; + pcurrent += 3; + pncurrent += 3; } } @@ -4554,29 +4904,29 @@ __attribute__((noinline)) void std_deinterlace_4field_bgr(uint8_t* col1, uint8_t pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - r = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - r = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 3; - pnabove += 3; - pcurrent += 3; - pncurrent += 3; + b = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + r = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + r = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 3; + pnabove += 3; + pcurrent += 3; + pncurrent += 3; } } @@ -4623,29 +4973,29 @@ __attribute__((noinline)) void std_deinterlace_4field_rgba(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - b = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - b = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + r = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + b = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + b = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4692,29 +5042,29 @@ __attribute__((noinline)) void std_deinterlace_4field_bgra(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - r = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - r = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + b = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + r = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + r = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4761,29 +5111,29 @@ __attribute__((noinline)) void std_deinterlace_4field_argb(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[1] - pabove[1]); - g = abs(pnabove[2] - pabove[2]); - b = abs(pnabove[3] - pabove[3]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[1] - pcurrent[1]); - g = abs(pncurrent[2] - pcurrent[2]); - b = abs(pncurrent[3] - pcurrent[3]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - pcurrent[3] = pabove[3]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + r = abs(pnabove[1] - pabove[1]); + g = abs(pnabove[2] - pabove[2]); + b = abs(pnabove[3] - pabove[3]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[1] - pcurrent[1]); + g = abs(pncurrent[2] - pcurrent[2]); + b = abs(pncurrent[3] - pcurrent[3]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + pcurrent[3] = pabove[3]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4830,896 +5180,28 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[1] - pabove[1]); - g = abs(pnabove[2] - pabove[2]); - r = abs(pnabove[3] - pabove[3]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[1] - pcurrent[1]); - g = abs(pncurrent[2] - pcurrent[2]); - r = abs(pncurrent[3] - pcurrent[3]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - pcurrent[3] = pabove[3]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + b = abs(pnabove[1] - pabove[1]); + g = abs(pnabove[2] - pabove[2]); + r = abs(pnabove[3] - pabove[3]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[1] - pcurrent[1]); + g = abs(pncurrent[2] - pcurrent[2]); + r = abs(pncurrent[3] - pcurrent[3]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + pcurrent[3] = pabove[3]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } - -/* Grayscale SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { - -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - union { - uint32_t int32; - uint8_t int8a[4]; - } threshold_mask; - threshold_mask.int8a[0] = threshold; - threshold_mask.int8a[1] = 0; - threshold_mask.int8a[2] = threshold; - threshold_mask.int8a[3] = 0; - - unsigned long row_width = width; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - /* Load the threshold */ - "mov %5, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_gray8:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_mask.int32) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* RGBA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,0,0,2,9,9,9,9,9,8,8,10}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_rgba:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,2,2,0,9,9,9,9,9,10,10,8}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_bgra:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,1,1,3,10,10,10,10,10,9,9,11}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_argb:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,3,3,1,10,10,10,10,10,11,11,9}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_abgr:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} diff --git a/src/zm_image.h b/src/zm_image.h index 0a01f1f18..1982d4232 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -54,7 +54,7 @@ extern imgbufcpy_fptr_t fptr_imgbufcpy; /* Should be called from Image class functions */ inline static uint8_t* AllocBuffer(size_t p_bufsize) { - uint8_t* buffer = (uint8_t*)zm_mallocaligned(16,p_bufsize); + uint8_t* buffer = (uint8_t*)zm_mallocaligned(64,p_bufsize); if(buffer == NULL) Fatal("Memory allocation failed: %s",strerror(errno)); @@ -122,8 +122,8 @@ protected: } public: - enum { CHAR_HEIGHT=11, CHAR_WIDTH=6 }; - enum { LINE_HEIGHT=CHAR_HEIGHT+0 }; + enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; + enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; protected: static bool initialised; @@ -264,6 +264,8 @@ public: /* Blend functions */ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); +void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); /* Delta functions */ @@ -274,6 +276,16 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); @@ -293,6 +305,9 @@ void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t* result, unsigned long count); @@ -309,8 +324,3 @@ void std_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int thre void std_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 186af1144..a4135d352 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -27,11 +27,11 @@ void* LibvlcLockBuffer(void* opaque, void** planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; data->mutex.lock(); - + uint8_t* buffer = data->buffer; data->buffer = data->prevBuffer; data->prevBuffer = buffer; - + *planes = data->buffer; return NULL; } @@ -39,7 +39,7 @@ void* LibvlcLockBuffer(void* opaque, void** planes) void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; - + bool newFrame = false; for(uint32_t i = 0; i < data->bufferSize; i++) { @@ -50,7 +50,7 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } data->mutex.unlock(); - + time_t now; time(&now); // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) @@ -61,8 +61,8 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } -LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) @@ -89,7 +89,7 @@ LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + if ( capture ) { Initialise(); @@ -143,9 +143,9 @@ void LibvlcCamera::Terminate() int LibvlcCamera::PrimeCapture() { Info("Priming capture from %s", mPath.c_str()); - + StringVector opVect = split(Options(), ","); - + // Set transport method as specified by method field, rtpUni is default if ( Method() == "rtpMulti" ) opVect.push_back("--rtsp-mcast"); @@ -168,11 +168,11 @@ int LibvlcCamera::PrimeCapture() mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV); if(mLibvlcInstance == NULL) Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg()); - + mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str()); if(mLibvlcMedia == NULL) Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg()); - + mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia); if(mLibvlcMediaPlayer == NULL) Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg()); @@ -182,18 +182,18 @@ int LibvlcCamera::PrimeCapture() mLibvlcData.bufferSize = width * height * mBpp; // Libvlc wants 32 byte alignment for images (should in theory do this for all image lines) - mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); - mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); + mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); + mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); mLibvlcData.newImage.setValueImmediate(false); libvlc_media_player_play(mLibvlcMediaPlayer); - + return(0); } int LibvlcCamera::PreCapture() -{ +{ return(0); } @@ -207,7 +207,21 @@ int LibvlcCamera::Capture( Image &image ) image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); mLibvlcData.newImage.setValueImmediate(false); mLibvlcData.mutex.unlock(); - + + return (0); +} + +// Should not return -1 as cancels capture. Always wait for image if available. +int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) +{ + while(!mLibvlcData.newImage.getValueImmediate()) + mLibvlcData.newImage.getUpdatedValue(1); + + mLibvlcData.mutex.lock(); + image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + mLibvlcData.newImage.setValueImmediate(false); + mLibvlcData.mutex.unlock(); + return (0); } diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 5f571adea..4221bd0b7 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -57,7 +57,7 @@ protected: libvlc_media_player_t *mLibvlcMediaPlayer; public: - LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~LibvlcCamera(); const std::string &Path() const { return( mPath ); } @@ -70,6 +70,7 @@ public: int PrimeCapture(); int PreCapture(); int Capture( Image &image ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); int PostCapture(); }; diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index e82115e70..f13733b11 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -55,7 +55,7 @@ static int vidioctl( int fd, int request, void *arg ) static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette ) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; - + #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { @@ -114,27 +114,27 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_UYVY : pixFormat = AV_PIX_FMT_UYVY422; break; - // These don't seem to have ffmpeg equivalents - // See if you can match any of the ones in the default clause below!? + // These don't seem to have ffmpeg equivalents + // See if you can match any of the ones in the default clause below!? case V4L2_PIX_FMT_RGB332 : case V4L2_PIX_FMT_RGB555X : case V4L2_PIX_FMT_RGB565X : - //case V4L2_PIX_FMT_Y16 : - //case V4L2_PIX_FMT_PAL8 : + //case V4L2_PIX_FMT_Y16 : + //case V4L2_PIX_FMT_PAL8 : case V4L2_PIX_FMT_YVU410 : case V4L2_PIX_FMT_YVU420 : case V4L2_PIX_FMT_Y41P : - //case V4L2_PIX_FMT_YUV555 : - //case V4L2_PIX_FMT_YUV565 : - //case V4L2_PIX_FMT_YUV32 : + //case V4L2_PIX_FMT_YUV555 : + //case V4L2_PIX_FMT_YUV565 : + //case V4L2_PIX_FMT_YUV32 : case V4L2_PIX_FMT_NV12 : case V4L2_PIX_FMT_NV21 : case V4L2_PIX_FMT_YYUV : case V4L2_PIX_FMT_HI240 : case V4L2_PIX_FMT_HM12 : - //case V4L2_PIX_FMT_SBGGR8 : - //case V4L2_PIX_FMT_SGBRG8 : - //case V4L2_PIX_FMT_SBGGR16 : + //case V4L2_PIX_FMT_SBGGR8 : + //case V4L2_PIX_FMT_SGBRG8 : + //case V4L2_PIX_FMT_SBGGR16 : case V4L2_PIX_FMT_DV : case V4L2_PIX_FMT_MPEG : case V4L2_PIX_FMT_WNVA : @@ -142,43 +142,43 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_PWC1 : case V4L2_PIX_FMT_PWC2 : case V4L2_PIX_FMT_ET61X251 : - //case V4L2_PIX_FMT_SPCA501 : - //case V4L2_PIX_FMT_SPCA505 : - //case V4L2_PIX_FMT_SPCA508 : - //case V4L2_PIX_FMT_SPCA561 : - //case V4L2_PIX_FMT_PAC207 : - //case V4L2_PIX_FMT_PJPG : - //case V4L2_PIX_FMT_YVYU : + //case V4L2_PIX_FMT_SPCA501 : + //case V4L2_PIX_FMT_SPCA505 : + //case V4L2_PIX_FMT_SPCA508 : + //case V4L2_PIX_FMT_SPCA561 : + //case V4L2_PIX_FMT_PAC207 : + //case V4L2_PIX_FMT_PJPG : + //case V4L2_PIX_FMT_YVYU : default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L2 @@ -188,17 +188,17 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette switch( palette ) { case VIDEO_PALETTE_RGB32 : - if(BigEndian) - pixFormat = AV_PIX_FMT_ARGB; - else - pixFormat = AV_PIX_FMT_BGRA; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_ARGB; + else + pixFormat = AV_PIX_FMT_BGRA; + break; case VIDEO_PALETTE_RGB24 : - if(BigEndian) - pixFormat = AV_PIX_FMT_RGB24; - else - pixFormat = AV_PIX_FMT_BGR24; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_RGB24; + else + pixFormat = AV_PIX_FMT_BGR24; + break; case VIDEO_PALETTE_GREY : pixFormat = AV_PIX_FMT_GRAY8; break; @@ -219,36 +219,36 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette pixFormat = AV_PIX_FMT_YUV420P; break; default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_YUVJ444P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_YUVJ444P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L1 @@ -286,8 +286,26 @@ AVFrame **LocalCamera::capturePictures = 0; LocalCamera *LocalCamera::last_camera = NULL; -LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int p_standard, bool p_v4l_multi_buffer, unsigned int p_v4l_captures_per_frame, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras) : - Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LocalCamera::LocalCamera( + int p_id, + const std::string &p_device, + int p_channel, + int p_standard, + bool p_v4l_multi_buffer, + unsigned int p_v4l_captures_per_frame, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras) : + Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), device( p_device ), channel( p_channel ), standard( p_standard ), @@ -301,7 +319,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, v4l_version = (p_method=="v4l2"?2:1); v4l_multi_buffer = p_v4l_multi_buffer; v4l_captures_per_frame = p_v4l_captures_per_frame; - + if ( capture ) { if ( device_prime ) @@ -322,9 +340,9 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, // We are the second, or subsequent, input using this channel channel_prime = false; } - + } - + /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ uint32_t checkval = 0xAABBCCDD; if(*(unsigned char*)&checkval == 0xDD) { @@ -337,7 +355,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; } - + #if ZM_HAS_V4L2 if( v4l_version == 2 && palette == 0 ) { /* Use automatic format selection */ @@ -348,27 +366,27 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, palette = V4L2_PIX_FMT_YUYV; } else { if(capture) { - Info("Selected capture palette: %s (%c%c%c%c)", palette_desc, palette&0xff, (palette>>8)&0xff, (palette>>16)&0xff, (palette>>24)&0xff); + Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); } } } #endif - + if( capture ) { if ( last_camera ) { if ( (p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1) ) Fatal( "Different Video For Linux version used for monitors sharing same device" ); - + if ( standard != last_camera->standard ) Warning( "Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( palette != last_camera->palette ) Warning( "Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( width != last_camera->width || height != last_camera->height ) Warning( "Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); } - + #if HAVE_LIBSWSCALE /* Get ffmpeg pixel format based on capture palette and endianness */ capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); @@ -380,38 +398,39 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_ARGB; - - /* BGR32 palette and 32bit target colourspace */ + + /* BGR32 palette and 32bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGRA; - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_RGB; - - /* BGR24 palette and 24bit target colourspace */ + + /* BGR24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) #if HAVE_LIBSWSCALE - Info("No direct match for the selected palette (%c%c%c%c) and target colorspace (%d). Format conversion is required, performance penalty expected", (capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff), colours ); + Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); #else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); + Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); #endif #if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ @@ -427,16 +446,18 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { #if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) if(!sws_isSupportedInput(capturePixFormat)) { - Error("swscale does not support the used capture format: %c%c%c%c",(capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff)); + Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } if(!sws_isSupportedOutput(imagePixFormat)) { - Error("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); + Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx", + (imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } #endif @@ -449,13 +470,13 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) { conversion_type = 2; } - + /* JPEG */ if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { Debug(2,"Using JPEG image decoding"); conversion_type = 3; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -500,11 +521,11 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } #endif // ZM_HAS_V4L2 - /* V4L1 format matching */ + /* V4L1 format matching */ #if ZM_HAS_V4L1 if ( v4l_version == 1) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; @@ -513,8 +534,8 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGRA; } - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == VIDEO_PALETTE_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; if(BigEndian) { @@ -522,12 +543,12 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGR; } - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == VIDEO_PALETTE_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); @@ -545,7 +566,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { if(!sws_isSupportedInput(capturePixFormat)) { @@ -565,7 +586,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422)) { conversion_type = 2; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -610,10 +631,10 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } } } -#endif // ZM_HAS_V4L1 +#endif // ZM_HAS_V4L1 last_camera = this; - Debug(3,"Selected subpixelorder: %d",subpixelorder); + Debug(3,"Selected subpixelorder: %u",subpixelorder); #if HAVE_LIBSWSCALE /* Initialize swscale stuff */ @@ -632,15 +653,15 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int pSize = avpicture_get_size( imagePixFormat, width, height ); #endif if( (unsigned int)pSize != imagesize) { - Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); + Fatal("Image size mismatch. Required: %d Available: %u",pSize,imagesize); } - + imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - + if ( !imgConversionContext ) { Fatal( "Unable to initialise image scaling context" ); - } - + } + } #endif } @@ -649,18 +670,14 @@ LocalCamera::~LocalCamera() { if ( device_prime && capture ) Terminate(); - + #if HAVE_LIBSWSCALE /* Clean up swscale stuff */ if(capture && conversion_type == 1) { sws_freeContext(imgConversionContext); imgConversionContext = NULL; - -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + av_frame_free( &tmpPicture ); -#else - av_freep( &tmpPicture ); -#endif } #endif } @@ -726,23 +743,23 @@ void LocalCamera::Initialise() v4l2_data.fmt.fmt.pix.height = height; v4l2_data.fmt.fmt.pix.pixelformat = palette; - if ( (extras & 0xff) != 0 ) - { - v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + if ( (extras & 0xff) != 0 ) { - Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); - v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); + + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + { + Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); + v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { + Fatal( "Failed to set video format: %s", strerror(errno) ); + } + } + } else { if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { Fatal( "Failed to set video format: %s", strerror(errno) ); } } - } else { - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Fatal( "Failed to set video format: %s", strerror(errno) ); - } - } /* Note VIDIOC_S_FMT may change width and height. */ Debug( 4, " v4l2_data.fmt.type = %08x", v4l2_data.fmt.type ); @@ -764,39 +781,39 @@ void LocalCamera::Initialise() if (v4l2_data.fmt.fmt.pix.sizeimage < min) v4l2_data.fmt.fmt.pix.sizeimage = min; - v4l2_jpegcompression jpeg_comp; - if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { - if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { - if(errno == EINVAL) { - Debug(2, "JPEG compression options are not available"); - } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno) ); - } - } else { - /* Set flags and quality. MJPEG should not have the huffman tables defined */ - if(palette == V4L2_PIX_FMT_MJPEG) { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; - } else { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; - } - jpeg_comp.quality = 85; - - /* Update the JPEG options */ - if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno) ); - } else { - if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { - Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + v4l2_jpegcompression jpeg_comp; + if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { + if(errno == EINVAL) { + Debug(2, "JPEG compression options are not available"); } else { - Debug(4, "JPEG quality: %d",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + Warning("Failed to get JPEG compression options: %s", strerror(errno) ); + } + } else { + /* Set flags and quality. MJPEG should not have the huffman tables defined */ + if(palette == V4L2_PIX_FMT_MJPEG) { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; + } else { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; + } + jpeg_comp.quality = 85; + + /* Update the JPEG options */ + if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { + Warning("Failed to set JPEG compression options: %s", strerror(errno) ); + } else { + if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { + Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + } else { + Debug(4, "JPEG quality: %d",jpeg_comp.quality); + Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + } } } } - } Debug( 3, "Setting up request buffers" ); - + memset( &v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs) ); if ( channel_count > 1 ) { Debug( 3, "Channel count is %d", channel_count ); @@ -853,27 +870,27 @@ void LocalCamera::Initialise() v4l2_data.buffers[i].start = mmap( NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset ); if ( v4l2_data.buffers[i].start == MAP_FAILED ) - Fatal( "Can't map video buffer %d (%d bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); + Fatal( "Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); #if HAVE_LIBSWSCALE #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); + capturePictures[i] = av_frame_alloc(); #else - capturePictures[i] = avcodec_alloc_frame(); + capturePictures[i] = avcodec_alloc_frame(); #endif - if ( !capturePictures[i] ) - Fatal( "Could not allocate picture" ); + if ( !capturePictures[i] ) + Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, 1); + av_image_fill_arrays(capturePictures[i]->data, + capturePictures[i]->linesize, + (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height, 1); #else - avpicture_fill( (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height ); + avpicture_fill( (AVPicture *)capturePictures[i], + (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height ); #endif #endif // HAVE_LIBSWSCALE } @@ -932,30 +949,30 @@ void LocalCamera::Initialise() switch (vid_pic.palette = palette) { case VIDEO_PALETTE_RGB32 : - { - vid_pic.depth = 32; - break; - } + { + vid_pic.depth = 32; + break; + } case VIDEO_PALETTE_RGB24 : - { - vid_pic.depth = 24; - break; - } + { + vid_pic.depth = 24; + break; + } case VIDEO_PALETTE_GREY : - { - vid_pic.depth = 8; - break; - } + { + vid_pic.depth = 8; + break; + } case VIDEO_PALETTE_RGB565 : case VIDEO_PALETTE_YUYV : case VIDEO_PALETTE_YUV422 : case VIDEO_PALETTE_YUV420P : case VIDEO_PALETTE_YUV422P : default: - { - vid_pic.depth = 16; - break; - } + { + vid_pic.depth = 16; + break; + } } if ( brightness >= 0 ) vid_pic.brightness = brightness; @@ -983,7 +1000,7 @@ void LocalCamera::Initialise() Debug( 4, "Old Y:%d", vid_win.y ); Debug( 4, "Old W:%d", vid_win.width ); Debug( 4, "Old H:%d", vid_win.height ); - + vid_win.x = 0; vid_win.y = 0; vid_win.width = width; @@ -1034,13 +1051,13 @@ void LocalCamera::Initialise() Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height, 1); + capturePictures[i]->linesize, + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)capturePictures[i], - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height ); + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height ); #endif } #endif // HAVE_LIBSWSCALE @@ -1079,7 +1096,7 @@ void LocalCamera::Initialise() Debug( 4, "New Y:%d", vid_win.y ); Debug( 4, "New W:%d", vid_win.width ); Debug( 4, "New H:%d", vid_win.height ); - + if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) Fatal( "Failed to get window data: %s", strerror(errno) ); @@ -1100,7 +1117,7 @@ void LocalCamera::Terminate() { Debug( 3, "Terminating video stream" ); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - // enum v4l2_buf_type type = v4l2_data.fmt.type; + // enum v4l2_buf_type type = v4l2_data.fmt.type; enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; if ( vidioctl( vid_fd, VIDIOC_STREAMOFF, &type ) < 0 ) Error( "Failed to stop capture stream: %s", strerror(errno) ); @@ -1110,44 +1127,44 @@ void LocalCamera::Terminate() #if HAVE_LIBSWSCALE /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif #endif if ( munmap( v4l2_data.buffers[i].start, v4l2_data.buffers[i].length ) < 0 ) Error( "Failed to munmap buffer %d: %s", i, strerror(errno) ); - } - + } + } else #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - if ( v4l_version == 1 ) - { + if ( v4l_version == 1 ) + { #if HAVE_LIBSWSCALE - for(int i=0; i < v4l1_data.frames.frames; i++) { - /* Free capture pictures */ + for(int i=0; i < v4l1_data.frames.frames; i++) { + /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif - } + } #endif - - Debug( 3, "Unmapping video buffers" ); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error( "Failed to munmap buffers: %s", strerror(errno) ); - delete[] v4l1_data.buffers; - } + Debug( 3, "Unmapping video buffers" ); + if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) + Error( "Failed to munmap buffers: %s", strerror(errno) ); + + delete[] v4l1_data.buffers; + } #endif // ZM_HAS_V4L1 close( vid_fd ); - + } uint32_t LocalCamera::AutoSelectFormat(int p_colours) { @@ -1160,13 +1177,13 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { unsigned int nIndex = 0; //int nRet = 0; // compiler say it isn't used int enum_fd; - + /* Open the device */ if ((enum_fd = open( device.c_str(), O_RDWR, 0 )) < 0) { Error( "Automatic format selection failed to open video device %s: %s", device.c_str(), strerror(errno) ); return selected_palette; } - + /* Enumerate available formats */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = nIndex; @@ -1177,14 +1194,15 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - Debug(6, "Got format: %s (%c%c%c%c) at index %d",fmt_desc[nIndex],fmt_fcc[nIndex]&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>24)&0xff ,nIndex); + Debug(6, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", + fmt_desc[nIndex], (fmt_fcc[nIndex]>>24)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex])&0xff ,nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = ++nIndex; fmtinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; } - + /* Select format */ int nIndexUsed = -1; unsigned int n_preferedformats = 0; @@ -1205,26 +1223,28 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { for( unsigned int i=0; i < n_preferedformats && nIndexUsed < 0; i++ ) { for( unsigned int j=0; j < nIndex; j++ ) { if( preferedformats[i] == fmt_fcc[j] ) { - Debug(6, "Choosing format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); /* Found a format! */ nIndexUsed = j; break; } else { - Debug(6, "No match for format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); } } } - + /* Have we found a match? */ if(nIndexUsed >= 0) { /* Found a match */ selected_palette = fmt_fcc[nIndexUsed]; strcpy(palette_desc,fmt_desc[nIndexUsed]); } - + /* Close the device */ close(enum_fd); - + #endif /* ZM_HAS_V4L2 */ return selected_palette; } @@ -1288,25 +1308,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Bus: %s\n", vid_cap.bus_info ); sprintf( output+strlen(output), " Version: %u.%u.%u\n", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff ); sprintf( output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.capabilities, - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), #ifdef V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), #else // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - "", + "", #endif // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), - capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), - capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), - capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), - capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) - ); + capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), + capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), + capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), + capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), + capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) + ); } else { @@ -1318,7 +1338,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } if ( verbose ) - sprintf( output+strlen(output), " Standards:\n" ); + sprintf( output+strlen(output), " Standards:\n" ); else sprintf( output+strlen(output), "S:" ); struct v4l2_standard standard; @@ -1347,14 +1367,14 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s\n", standard.name ); + sprintf( output+strlen(output), " %s\n", standard.name ); else sprintf( output+strlen(output), "%s/", standard.name ); } while ( standardIndex++ >= 0 ); if ( !verbose && output[strlen(output)-1] == '/') output[strlen(output)-1] = '|'; - + if ( verbose ) sprintf( output+strlen(output), " Formats:\n" ); else @@ -1385,63 +1405,65 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + format.description, (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, format.pixelformat&0xff); else - sprintf( output+strlen(output), "%c%c%c%c/", format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), "0x%02hhx%02hhx%02hhx%02hhx/", + (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat)&0xff); } while ( formatIndex++ >= 0 ); if ( !verbose ) output[strlen(output)-1] = '|'; - if(verbose) - sprintf( output+strlen(output), "Crop Capabilities\n" ); - - struct v4l2_cropcap cropcap; - memset( &cropcap, 0, sizeof(cropcap) ); - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) - { - if(errno != EINVAL) { - /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop capabilities: %s", strerror(errno) ); - } - - if(verbose) { - sprintf( output+strlen(output), " Cropping is not supported\n"); - } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - struct v4l2_crop crop; - memset( &crop, 0, sizeof(crop) ); - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) - { - if ( errno != EINVAL ) + if(verbose) + sprintf( output+strlen(output), "Crop Capabilities\n" ); + + struct v4l2_cropcap cropcap; + memset( &cropcap, 0, sizeof(cropcap) ); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) { - /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop: %s", strerror(errno) ); - } - - if ( verbose ) { - sprintf( output+strlen(output), " Cropping is not supported\n"); + if(errno != EINVAL) { + /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop capabilities: %s", strerror(errno) ); + } + + if(verbose) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - /* Cropping supported */ - if ( verbose ) { - sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); - sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); - sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); - } else { - sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); - } - } - } /* Crop code */ + struct v4l2_crop crop; + memset( &crop, 0, sizeof(crop) ); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) + { + if ( errno != EINVAL ) + { + /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop: %s", strerror(errno) ); + } + + if ( verbose ) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } + } else { + /* Cropping supported */ + if ( verbose ) { + sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); + sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); + sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); + } else { + sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); + } + } + } /* Crop code */ struct v4l2_input input; int inputIndex = 0; @@ -1505,16 +1527,16 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return( false ); } if ( verbose ) { sprintf( output+strlen(output), " Input %d\n", input.index ); - sprintf( output+strlen(output), " Name: %s\n", input.name ); - sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); - sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); - sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); + sprintf( output+strlen(output), " Name: %s\n", input.name ); + sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); + sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); + sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); } else { @@ -1525,10 +1547,10 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers if ( verbose ) { - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); } else { @@ -1562,21 +1584,21 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), "Video Capabilities\n" ); sprintf( output+strlen(output), " Name: %s\n", vid_cap.name ); sprintf( output+strlen(output), " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.type, - vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", - vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", - vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", - vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", - vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", - vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", - vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", - vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", - vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", - vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", - vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" - ); + vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", + vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", + vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", + vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", + vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", + vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", + vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", + vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", + vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", + vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", + vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" + ); sprintf( output+strlen(output), " Video Channels: %d\n", vid_cap.channels ); sprintf( output+strlen(output), " Audio Channels: %d\n", vid_cap.audios ); sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxwidth ); @@ -1638,25 +1660,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers { sprintf( output+strlen(output), "Picture Attributes\n" ); sprintf( output+strlen(output), " Palette: %d - %s\n", vid_pic.palette, - vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( - vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( - vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( - vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( - vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( - vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( - vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" - )))))))))))))))))); + vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( + vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( + vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( + vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( + vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( + vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( + vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" + )))))))))))))))))); sprintf( output+strlen(output), " Colour Depth: %d\n", vid_pic.depth ); sprintf( output+strlen(output), " Brightness: %d\n", vid_pic.brightness ); sprintf( output+strlen(output), " Hue: %d\n", vid_pic.hue ); @@ -1695,19 +1717,19 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Name: %s\n", vid_src.name ); sprintf( output+strlen(output), " Channel: %d\n", vid_src.channel ); sprintf( output+strlen(output), " Flags: %d\n%s%s", vid_src.flags, - vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", - vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" - ); + vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", + vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" + ); sprintf( output+strlen(output), " Type: %d - %s\n", vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - )); + vid_src.type==VIDEO_TYPE_TV?"TV":( + vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" + )); sprintf( output+strlen(output), " Format: %d - %s\n", vid_src.norm, - vid_src.norm==VIDEO_MODE_PAL?"PAL":( - vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( - vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( - vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" - )))); + vid_src.norm==VIDEO_MODE_PAL?"PAL":( + vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( + vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( + vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" + )))); } else { @@ -1746,7 +1768,7 @@ int LocalCamera::Brightness( int p_brightness ) Error( "Unable to query brightness: %s", strerror(errno) ) else Warning( "Brightness control is not supported" ) - //Info( "Brightness 1 %d", vid_control.value ); + //Info( "Brightness 1 %d", vid_control.value ); } else if ( p_brightness >= 0 ) { @@ -2032,7 +2054,7 @@ int LocalCamera::Capture( Image &image ) static uint8_t* directbuffer = NULL; static int capture_frame = -1; int buffer_bytesused = 0; - + int captures_per_frame = 1; if ( channel_count > 1 ) captures_per_frame = v4l_captures_per_frame; @@ -2040,8 +2062,8 @@ int LocalCamera::Capture( Image &image ) captures_per_frame = 1; Warning( "Invalid Captures Per Frame setting: %d", captures_per_frame ); } - - + + // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer if ( channel_prime ) { @@ -2065,7 +2087,7 @@ int LocalCamera::Capture( Image &image ) Warning( "Capture failure, possible signal loss?: %s", strerror(errno) ) else Error( "Unable to capture frame %d: %s", vid_buf.index, strerror(errno) ) - return( -1 ); + return( -1 ); } v4l2_data.bufptr = &vid_buf; @@ -2086,9 +2108,9 @@ int LocalCamera::Capture( Image &image ) buffer_bytesused = v4l2_data.bufptr->bytesused; if((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height)) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); + Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); } - + } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 @@ -2120,12 +2142,12 @@ int LocalCamera::Capture( Image &image ) buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; } #endif // ZM_HAS_V4L1 - } /* prime capture */ - + } /* prime capture */ + if(conversion_type != 0) { - + Debug( 3, "Performing format conversion" ); - + /* Request a writeable buffer of the target image */ directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); if(directbuffer == NULL) { @@ -2139,36 +2161,36 @@ int LocalCamera::Capture( Image &image ) /* Use swscale to convert the image directly into the shared memory */ #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmpPicture->data, - tmpPicture->linesize, directbuffer, - imagePixFormat, width, height, 1); + tmpPicture->linesize, directbuffer, + imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)tmpPicture, directbuffer, - imagePixFormat, width, height ); + imagePixFormat, width, height ); #endif sws_scale( imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, tmpPicture->linesize ); } #endif if(conversion_type == 2) { - + Debug( 9, "Calling the conversion function" ); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); } else if(conversion_type == 3) { - + Debug( 9, "Decoding the JPEG image" ); /* JPEG decoding */ image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } - + } else { Debug( 3, "No format conversion performed. Assigning the image" ); - + /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ image.Assign( width, height, colours, subpixelorder, buffer, imagesize); - + } - + return( 0 ); } diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 785709c2c..dae5f830e 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -23,6 +23,7 @@ #include "zm.h" #include "zm_camera.h" #include "zm_image.h" +#include "zm_packetqueue.h" #if ZM_HAS_V4L @@ -52,31 +53,31 @@ class LocalCamera : public Camera { protected: #if ZM_HAS_V4L2 - struct V4L2MappedBuffer - { - void *start; - size_t length; - }; + struct V4L2MappedBuffer + { + void *start; + size_t length; + }; - struct V4L2Data - { - v4l2_cropcap cropcap; - v4l2_crop crop; - v4l2_format fmt; - v4l2_requestbuffers reqbufs; - V4L2MappedBuffer *buffers; - v4l2_buffer *bufptr; - }; + struct V4L2Data + { + v4l2_cropcap cropcap; + v4l2_crop crop; + v4l2_format fmt; + v4l2_requestbuffers reqbufs; + V4L2MappedBuffer *buffers; + v4l2_buffer *bufptr; + }; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - struct V4L1Data - { - int active_frame; - video_mbuf frames; - video_mmap *buffers; - unsigned char *bufptr; - }; + struct V4L1Data + { + int active_frame; + video_mbuf frames; + video_mmap *buffers; + unsigned char *bufptr; + }; #endif // ZM_HAS_V4L1 protected: @@ -104,24 +105,42 @@ protected: unsigned int v4l_captures_per_frame; #if ZM_HAS_V4L2 - static V4L2Data v4l2_data; + static V4L2Data v4l2_data; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - static V4L1Data v4l1_data; + static V4L1Data v4l1_data; #endif // ZM_HAS_V4L1 #if HAVE_LIBSWSCALE - static AVFrame **capturePictures; - _AVPIXELFORMAT imagePixFormat; - _AVPIXELFORMAT capturePixFormat; + static AVFrame **capturePictures; + _AVPIXELFORMAT imagePixFormat; + _AVPIXELFORMAT capturePixFormat; struct SwsContext *imgConversionContext; - AVFrame *tmpPicture; + AVFrame *tmpPicture; #endif // HAVE_LIBSWSCALE - static LocalCamera *last_camera; + static LocalCamera *last_camera; public: - LocalCamera( int p_id, const std::string &device, int p_channel, int p_format, bool v4lmultibuffer, unsigned int v4lcapturesperframe, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras = 0); + LocalCamera( + int p_id, + const std::string &device, + int p_channel, + int p_format, + bool v4lmultibuffer, + unsigned int v4lcapturesperframe, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras = 0); ~LocalCamera(); void Initialise(); @@ -143,6 +162,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); }; diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index dee423503..78d9ab934 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -31,9 +31,9 @@ #include #include #include +#include #ifdef __FreeBSD__ #include -#include #endif bool Logger::smInitialised = false; @@ -43,23 +43,18 @@ Logger::StringMap Logger::smCodes; Logger::IntMap Logger::smSyslogPriorities; #if 0 -static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) -{ +static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) { tp1->tv_sec -= tp2->tv_sec; - if ( tp1->tv_usec <= tp2->tv_usec ) - { + if ( tp1->tv_usec <= tp2->tv_usec ) { tp1->tv_sec--; tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); - } - else - { + } else { tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; } } #endif -void Logger::usrHandler( int sig ) -{ +void Logger::usrHandler( int sig ) { Logger *logger = fetch(); if ( sig == SIGUSR1 ) logger->level( logger->level()+1 ); @@ -80,15 +75,13 @@ Logger::Logger() : mDbConnected( false ), mLogFileFP( NULL ), mHasTerm( false ), - mFlush( false ) -{ - if ( smInstance ) - { + mFlush( false ) { + + if ( smInstance ) { Panic( "Attempt to create second instance of Logger class" ); } - if ( !smInitialised ) - { + if ( !smInitialised ) { smCodes[INFO] = "INF"; smCodes[WARNING] = "WAR"; smCodes[ERROR] = "ERR"; @@ -103,8 +96,7 @@ Logger::Logger() : smSyslogPriorities[PANIC] = LOG_ERR; char code[4] = ""; - for ( int i = DEBUG1; i <= DEBUG9; i++ ) - { + for ( int i = DEBUG1; i <= DEBUG9; i++ ) { snprintf( code, sizeof(code), "DB%d", i ); smCodes[i] = code; smSyslogPriorities[i] = LOG_DEBUG; @@ -117,21 +109,18 @@ Logger::Logger() : mHasTerm = true; } -Logger::~Logger() -{ +Logger::~Logger() { terminate(); } -void Logger::initialise( const std::string &id, const Options &options ) -{ +void Logger::initialise( const std::string &id, const Options &options ) { char *envPtr; if ( !id.empty() ) this->id( id ); std::string tempLogFile; - if ( options.mLogPath.size() ) - { + if ( options.mLogPath.size() ) { mLogPath = options.mLogPath; tempLogFile = mLogPath+"/"+mId+".log"; } @@ -179,26 +168,22 @@ void Logger::initialise( const std::string &id, const Options &options ) if ( (envPtr = getTargettedEnv( "LOG_LEVEL_SYSLOG" )) ) tempSyslogLevel = atoi(envPtr); - if ( config.log_debug ) - { + if ( config.log_debug ) { StringVector targets = split( config.log_debug_target, "|" ); - for ( unsigned int i = 0; i < targets.size(); i++ ) - { + for ( unsigned int i = 0; i < targets.size(); i++ ) { const std::string &target = targets[i]; - if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) - { - if ( config.log_debug_level > NOLOG ) - { + if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) { + if ( config.log_debug_level > NOLOG ) { tempLevel = config.log_debug_level; - if ( config.log_debug_file[0] ) - { + if ( config.log_debug_file[0] ) { tempLogFile = config.log_debug_file; tempFileLevel = tempLevel; } } } - } - } + } // end foreach target + } // end if config.log_debug + logFile( tempLogFile ); @@ -212,30 +197,34 @@ void Logger::initialise( const std::string &id, const Options &options ) mFlush = (envPtr = getenv( "LOG_FLUSH")) ? atoi( envPtr ) : false; //mRuntime = (envPtr = getenv( "LOG_RUNTIME")) ? atoi( envPtr ) : false; - { struct sigaction action; memset( &action, 0, sizeof(action) ); action.sa_handler = usrHandler; action.sa_flags = SA_RESTART; - if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) - { + if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) { Fatal( "sigaction(), error = %s", strerror(errno) ); } - if ( sigaction( SIGUSR2, &action, 0 ) < 0) - { + if ( sigaction( SIGUSR2, &action, 0 ) < 0) { Fatal( "sigaction(), error = %s", strerror(errno) ); } } mInitialised = true; - Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", smCodes[mLevel].c_str(), smCodes[mEffectiveLevel].c_str(), smCodes[mTermLevel].c_str(), smCodes[mDatabaseLevel].c_str(), smCodes[mFileLevel].c_str(), mLogFile.c_str(), smCodes[mSyslogLevel].c_str() ); + Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", + smCodes[mLevel].c_str(), + smCodes[mEffectiveLevel].c_str(), + smCodes[mTermLevel].c_str(), + smCodes[mDatabaseLevel].c_str(), + smCodes[mFileLevel].c_str(), + mLogFile.c_str(), + smCodes[mSyslogLevel].c_str() + ); } -void Logger::terminate() -{ +void Logger::terminate() { Debug(1, "Terminating Logger" ); if ( mFileLevel > NOLOG ) @@ -248,33 +237,28 @@ void Logger::terminate() closeDatabase(); } -bool Logger::boolEnv( const std::string &name, bool defaultValue ) -{ +bool Logger::boolEnv( const std::string &name, bool defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? atoi( envPtr ) : defaultValue ); } -int Logger::intEnv( const std::string &name, bool defaultValue ) -{ +int Logger::intEnv( const std::string &name, bool defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? atoi( envPtr ) : defaultValue ); } -std::string Logger::strEnv( const std::string &name, const std::string defaultValue ) -{ +std::string Logger::strEnv( const std::string &name, const std::string defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? envPtr : defaultValue ); } -char *Logger::getTargettedEnv( const std::string &name ) -{ +char *Logger::getTargettedEnv( const std::string &name ) { char *envPtr = NULL; std::string envName; envName = name+"_"+mId; envPtr = getenv( envName.c_str() ); - if ( !envPtr && mId != mIdRoot ) - { + if ( !envPtr && mId != mIdRoot ) { envName = name+"_"+mIdRoot; envPtr = getenv( envName.c_str() ); } @@ -283,27 +267,22 @@ char *Logger::getTargettedEnv( const std::string &name ) return( envPtr ); } -const std::string &Logger::id( const std::string &id ) -{ +const std::string &Logger::id( const std::string &id ) { std::string tempId = id; size_t pos; // Remove whitespace - while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) - { + while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) { tempId.replace( pos, 1, "" ); } // Replace non-alphanum with underscore - while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) - { + while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) { tempId.replace( pos, 1, "_" ); } - if ( mId != tempId ) - { + if ( mId != tempId ) { mId = tempId; pos = mId.find( '_' ); - if ( pos != std::string::npos ) - { + if ( pos != std::string::npos ) { mIdRoot = mId.substr( 0, pos ); if ( ++pos < mId.size() ) mIdArgs = mId.substr( pos ); @@ -312,10 +291,8 @@ const std::string &Logger::id( const std::string &id ) return( mId ); } -Logger::Level Logger::level( Logger::Level level ) -{ - if ( level > NOOPT ) - { +Logger::Level Logger::level( Logger::Level level ) { + if ( level > NOOPT ) { level = limit(level); if ( mLevel != level ) mLevel = level; @@ -335,10 +312,8 @@ Logger::Level Logger::level( Logger::Level level ) return( mLevel ); } -Logger::Level Logger::termLevel( Logger::Level termLevel ) -{ - if ( termLevel > NOOPT ) - { +Logger::Level Logger::termLevel( Logger::Level termLevel ) { + if ( termLevel > NOOPT ) { if ( !mHasTerm ) termLevel = NOLOG; termLevel = limit(termLevel); @@ -348,19 +323,13 @@ Logger::Level Logger::termLevel( Logger::Level termLevel ) return( mTermLevel ); } -Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) -{ - if ( databaseLevel > NOOPT ) - { +Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) { + if ( databaseLevel > NOOPT ) { databaseLevel = limit(databaseLevel); - if ( mDatabaseLevel != databaseLevel ) - { - if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) - { - if ( !mDbConnected ) - { - if ( !mysql_init( &mDbConnection ) ) - { + if ( mDatabaseLevel != databaseLevel ) { + if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) { + if ( !mDbConnected ) { + if ( !mysql_init( &mDbConnection ) ) { Fatal( "Can't initialise database connection: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } @@ -368,60 +337,48 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) ); std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" ); - if ( colonIndex == std::string::npos ) - { - if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) - { + if ( colonIndex == std::string::npos ) { + if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) { Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } - } - else - { + } else { std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 ); - if ( dbPortOrSocket[0] == '/' ) - { - if ( !mysql_real_connect( &mDbConnection, NULL, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, dbPortOrSocket.c_str(), 0 ) ) - { + if ( dbPortOrSocket[0] == '/' ) { + if ( !mysql_real_connect( &mDbConnection, NULL, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, dbPortOrSocket.c_str(), 0 ) ) { Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } - } - else - { - if ( !mysql_real_connect( &mDbConnection, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, atoi(dbPortOrSocket.c_str()), NULL, 0 ) ) - { + } else { + if ( !mysql_real_connect( &mDbConnection, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, atoi(dbPortOrSocket.c_str()), NULL, 0 ) ) { Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } } - } + } // end if has colon unsigned long mysqlVersion = mysql_get_server_version( &mDbConnection ); if ( mysqlVersion < 50019 ) if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) ); - if ( mysql_select_db( &mDbConnection, staticConfig.DB_NAME.c_str() ) ) - { + if ( mysql_select_db( &mDbConnection, staticConfig.DB_NAME.c_str() ) ) { Fatal( "Can't select database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } mDbConnected = true; - } - } + } // end if ! mDbConnected + } // end if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) mDatabaseLevel = databaseLevel; - } - } + } // end if ( mDatabaseLevel != databaseLevel ) + } // end if ( databaseLevel > NOOPT ) + return( mDatabaseLevel ); } -Logger::Level Logger::fileLevel( Logger::Level fileLevel ) -{ - if ( fileLevel > NOOPT ) - { +Logger::Level Logger::fileLevel( Logger::Level fileLevel ) { + if ( fileLevel > NOOPT ) { fileLevel = limit(fileLevel); - if ( mFileLevel != fileLevel ) - { + if ( mFileLevel != fileLevel ) { if ( mFileLevel > NOLOG ) closeFile(); mFileLevel = fileLevel; @@ -432,13 +389,10 @@ Logger::Level Logger::fileLevel( Logger::Level fileLevel ) return( mFileLevel ); } -Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) -{ - if ( syslogLevel > NOOPT ) - { +Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) { + if ( syslogLevel > NOOPT ) { syslogLevel = limit(syslogLevel); - if ( mSyslogLevel != syslogLevel ) - { + if ( mSyslogLevel != syslogLevel ) { if ( mSyslogLevel > NOLOG ) closeSyslog(); mSyslogLevel = syslogLevel; @@ -449,12 +403,10 @@ Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) return( mSyslogLevel ); } -void Logger::logFile( const std::string &logFile ) -{ +void Logger::logFile( const std::string &logFile ) { bool addLogPid = false; std::string tempLogFile = logFile; - if ( tempLogFile[tempLogFile.length()-1] == '+' ) - { + if ( tempLogFile[tempLogFile.length()-1] == '+' ) { tempLogFile.resize(tempLogFile.length()-1); addLogPid = true; } @@ -464,175 +416,151 @@ void Logger::logFile( const std::string &logFile ) mLogFile = tempLogFile; } -void Logger::openFile() -{ - if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) - { +void Logger::openFile() { + if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) { mFileLevel = NOLOG; Fatal( "fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno) ); } } -void Logger::closeFile() -{ - if ( mLogFileFP ) - { +void Logger::closeFile() { + if ( mLogFileFP ) { fflush( mLogFileFP ); - if ( fclose( mLogFileFP ) < 0 ) - { + if ( fclose( mLogFileFP ) < 0 ) { Fatal( "fclose(), error = %s",strerror(errno) ); } mLogFileFP = (FILE *)NULL; } } -void Logger::closeDatabase() -{ - if ( mDbConnected ) - { +void Logger::closeDatabase() { + if ( mDbConnected ) { mysql_close( &mDbConnection ); mDbConnected = false; } } -void Logger::openSyslog() -{ +void Logger::openSyslog() { (void) openlog( mId.c_str(), LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); } -void Logger::closeSyslog() -{ +void Logger::closeSyslog() { (void) closelog(); } -void Logger::logPrint( bool hex, const char * const filepath, const int line, const int level, const char *fstring, ... ) -{ - if ( level <= mEffectiveLevel ) - { - char classString[4]; - char timeString[64]; - char logString[8192]; - va_list argPtr; +void Logger::logPrint( bool hex, const char * const filepath, const int line, const int level, const char *fstring, ... ) { + if ( level <= mEffectiveLevel ) { + char timeString[64]; + char logString[8192]; + va_list argPtr; struct timeval timeVal; - const char * const file = basename(filepath); - + char *filecopy = strdup(filepath); + const char * const file = basename(filecopy); + const char *classString = smCodes[level].c_str(); + if ( level < PANIC || level > DEBUG9 ) Panic( "Invalid logger level %d", level ); - strncpy( classString, smCodes[level].c_str(), sizeof(classString) ); - gettimeofday( &timeVal, NULL ); - #if 0 - if ( logRuntime ) - { +#if 0 + if ( logRuntime ) { static struct timeval logStart; subtractTime( &timeVal, &logStart ); snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); - } - else - { - #endif + } else { +#endif char *timePtr = timeString; timePtr += strftime( timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec) ); snprintf( timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec ); - #if 0 +#if 0 } - #endif +#endif pid_t tid; #ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; + long lwpid; + thr_self(&lwpid); + tid = lwpid; if (tid < 0 ) // Thread/Process id #else #ifdef HAVE_SYSCALL - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id +#ifdef __FreeBSD_kernel__ + if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - # else - // SOLARIS doesn't have SYS_gettid; don't assume - #ifdef SYS_gettid - if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id - #endif // SYS_gettid - #endif +# else + // SOLARIS doesn't have SYS_gettid; don't assume +#ifdef SYS_gettid + if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id +#endif // SYS_gettid +#endif #endif // HAVE_SYSCALL #endif - tid = getpid(); // Process id + tid = getpid(); // Process id char *logPtr = logString; logPtr += snprintf( logPtr, sizeof(logString), "%s %s[%d].%s-%s/%d [", - timeString, - mId.c_str(), - tid, - classString, - file, - line + timeString, + mId.c_str(), + tid, + classString, + file, + line ); char *syslogStart = logPtr; va_start( argPtr, fstring ); - if ( hex ) - { + if ( hex ) { unsigned char *data = va_arg( argPtr, unsigned char * ); int len = va_arg( argPtr, int ); int i; logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), "%d:", len ); - for ( i = 0; i < len; i++ ) - { + for ( i = 0; i < len; i++ ) { logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i] ); } - } - else - { + } else { logPtr += vsnprintf( logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr ); } va_end(argPtr); char *syslogEnd = logPtr; strncpy( logPtr, "]\n", sizeof(logString)-(logPtr-logString) ); - if ( level <= mTermLevel ) - { + if ( level <= mTermLevel ) { printf( "%s", logString ); fflush( stdout ); } - if ( level <= mFileLevel ) - { + if ( level <= mFileLevel ) { fprintf( mLogFileFP, "%s", logString ); if ( mFlush ) fflush( mLogFileFP ); } *syslogEnd = '\0'; - if ( level <= mDatabaseLevel ) - { + if ( level <= mDatabaseLevel ) { char sql[ZM_SQL_MED_BUFSIZ]; char escapedString[(strlen(syslogStart)*2)+1]; mysql_real_escape_string( &mDbConnection, escapedString, syslogStart, strlen(syslogStart) ); - + snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); - if ( mysql_query( &mDbConnection, sql ) ) - { + if ( mysql_query( &mDbConnection, sql ) ) { Level tempDatabaseLevel = mDatabaseLevel; databaseLevel( NOLOG ); Error( "Can't insert log entry: sql(%s) error(%s)", sql, mysql_error( &mDbConnection ) ); databaseLevel(tempDatabaseLevel); } } - if ( level <= mSyslogLevel ) - { + if ( level <= mSyslogLevel ) { int priority = smSyslogPriorities[level]; //priority |= LOG_DAEMON; - syslog( priority, "%s [%s]", classString, syslogStart ); + syslog( priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart ); } - if ( level <= FATAL ) - { + free(filecopy); + if ( level <= FATAL ) { if ( level <= PANIC ) abort(); exit( -1 ); @@ -640,8 +568,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co } } -void logInit( const char *name, const Logger::Options &options ) -{ +void logInit( const char *name, const Logger::Options &options ) { if ( !Logger::smInstance ) Logger::smInstance = new Logger(); Logger::Options tempOptions = options; @@ -649,8 +576,7 @@ void logInit( const char *name, const Logger::Options &options ) Logger::smInstance->initialise( name, tempOptions ); } -void logTerm() -{ +void logTerm() { if ( Logger::smInstance ) delete Logger::smInstance; } diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index b7920c166..392582a21 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -28,6 +28,7 @@ #include "zm_mpeg.h" #include "zm_signal.h" #include "zm_monitor.h" +#include "zm_video.h" #if ZM_HAS_V4L #include "zm_local_camera.h" #endif // ZM_HAS_V4L @@ -74,8 +75,7 @@ std::vector split(const std::string &s, char delim) { -Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) -{ +Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) { strncpy( name, p_name, sizeof(name) ); #if ZM_MEM_MAPPED @@ -94,15 +94,12 @@ Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) connected = false; } -Monitor::MonitorLink::~MonitorLink() -{ +Monitor::MonitorLink::~MonitorLink() { disconnect(); } -bool Monitor::MonitorLink::connect() -{ - if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) - { +bool Monitor::MonitorLink::connect() { + if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) { last_connect_time = time( 0 ); mem_size = sizeof(SharedData) + sizeof(TriggerData); @@ -110,8 +107,7 @@ bool Monitor::MonitorLink::connect() Debug( 1, "link.mem.size=%d", mem_size ); #if ZM_MEM_MAPPED map_fd = open( mem_file, O_RDWR, (mode_t)0600 ); - if ( map_fd < 0 ) - { + if ( map_fd < 0 ) { Debug( 3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); @@ -124,44 +120,37 @@ bool Monitor::MonitorLink::connect() } struct stat map_stat; - if ( fstat( map_fd, &map_stat ) < 0 ) - { + if ( fstat( map_fd, &map_stat ) < 0 ) { Error( "Can't stat linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); } - if ( map_stat.st_size == 0 ) - { + if ( map_stat.st_size == 0 ) { Error( "Linked memory map file %s is empty: %s", mem_file, strerror(errno) ); disconnect(); return( false ); - } - else if ( map_stat.st_size < mem_size ) - { + } else if ( map_stat.st_size < mem_size ) { Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size ); disconnect(); return( false ); } mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 ); - if ( mem_ptr == MAP_FAILED ) - { + if ( mem_ptr == MAP_FAILED ) { Error( "Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno) ); disconnect(); return( false ); } #else // ZM_MEM_MAPPED shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 ); - if ( shm_id < 0 ) - { + if ( shm_id < 0 ) { Debug( 3, "Can't shmget link memory: %s", strerror(errno) ); connected = false; return( false ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < (void *)0 ) { Debug( 3, "Can't shmat link memory: %s", strerror(errno) ); connected = false; return( false ); @@ -171,8 +160,7 @@ bool Monitor::MonitorLink::connect() shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - if ( !shared_data->valid ) - { + if ( !shared_data->valid ) { Debug( 3, "Linked memory not initialised by capture daemon" ); disconnect(); return( false ); @@ -187,15 +175,12 @@ bool Monitor::MonitorLink::connect() return( false ); } -bool Monitor::MonitorLink::disconnect() -{ - if ( connected ) - { +bool Monitor::MonitorLink::disconnect() { + if ( connected ) { connected = false; #if ZM_MEM_MAPPED - if ( mem_ptr > 0 ) - { + if ( mem_ptr > (void *)0 ) { msync( mem_ptr, mem_size, MS_ASYNC ); munmap( mem_ptr, mem_size ); } @@ -205,25 +190,21 @@ bool Monitor::MonitorLink::disconnect() map_fd = -1; #else // ZM_MEM_MAPPED struct shmid_ds shm_data; - if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) - { + if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } shm_id = 0; - if ( shm_data.shm_nattch <= 1 ) - { - if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) - { + if ( shm_data.shm_nattch <= 1 ) { + if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } } - if ( shmdt( mem_ptr ) < 0 ) - { + if ( shmdt( mem_ptr ) < 0 ) { Debug( 3, "Can't shmdt: %s", strerror(errno) ); return( false ); } @@ -235,32 +216,24 @@ bool Monitor::MonitorLink::disconnect() return( true ); } -bool Monitor::MonitorLink::isAlarmed() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::isAlarmed() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM ); } -bool Monitor::MonitorLink::inAlarm() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::inAlarm() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM || shared_data->state == ALERT ); } -bool Monitor::MonitorLink::hasAlarmed() -{ - if ( shared_data->state == ALARM ) - { +bool Monitor::MonitorLink::hasAlarmed() { + if ( shared_data->state == ALARM ) { return( true ); - } - else if( shared_data->last_event != (unsigned int)last_event ) - { + } else if ( shared_data->last_event != (unsigned int)last_event ) { last_event = shared_data->last_event; } return( false ); @@ -276,6 +249,10 @@ Monitor::Monitor( Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, @@ -310,6 +287,10 @@ Monitor::Monitor( height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), orientation( (Orientation)p_orientation ), deinterlacing( p_deinterlacing ), + savejpegspref( p_savejpegs ), + videowriter( p_videowriter ), + encoderparams( p_encoderparams ), + record_audio( p_record_audio ), label_coord( p_label_coord ), label_size( p_label_size ), image_buffer_count( p_image_buffer_count ), @@ -350,21 +331,20 @@ Monitor::Monitor( // Change \n to actual line feeds char *token_ptr = label_format; const char *token_string = "\n"; - while( ( token_ptr = strstr( token_ptr, token_string ) ) ) - { - if ( *(token_ptr+1) ) - { + while( ( token_ptr = strstr( token_ptr, token_string ) ) ) { + if ( *(token_ptr+1) ) { *token_ptr = '\n'; token_ptr++; strcpy( token_ptr, token_ptr+1 ); - } - else - { + } else { *token_ptr = '\0'; break; } } + /* Parse encoder parameters */ + ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); + fps = 0.0; event_count = 0; image_count = 0; @@ -391,9 +371,10 @@ Monitor::Monitor( mem_size = sizeof(SharedData) + sizeof(TriggerData) + + sizeof(VideoStoreData) //Information to pass back to the capture process + (image_buffer_count*sizeof(struct timeval)) + (image_buffer_count*camera->ImageSize()) - + 64; /* Padding used to permit aligning the images buffer to 16 byte boundary */ + + 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */ Debug( 1, "mem.size=%d", mem_size ); mem_ptr = NULL; @@ -426,6 +407,10 @@ Monitor::Monitor( trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; shared_data->valid = true; + video_store_data->recording = (struct timeval){0}; + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); + video_store_data->size = sizeof(VideoStoreData); + //video_store_data->frameNumber = 0; } else if ( purpose == ANALYSIS ) { this->connect(); if ( ! mem_ptr ) exit(-1); @@ -435,16 +420,15 @@ Monitor::Monitor( shared_data->alarm_y = -1; } - if ( ( ! mem_ptr ) || ! shared_data->valid ) - { - if ( purpose != QUERY ) - { + if ( ( ! mem_ptr ) || ! shared_data->valid ) { + if ( purpose != QUERY ) { Error( "Shared data not initialised by capture daemon for monitor %s", name ); exit( -1 ); } } // Will this not happen every time a monitor is instantiated? Seems like all the calls to the Monitor constructor pass a zero for n_zones, then load zones after.. + // In my storage areas branch, I took this out.. and didn't notice any problems. if ( !n_zones ) { Debug( 1, "Monitor %s has no zones, adding one.", name ); n_zones = 1; @@ -460,8 +444,10 @@ Monitor::Monitor( Debug( 1, "Monitor %s LBF = '%s', LBX = %d, LBY = %d, LBS = %d", name, label_format, label_coord.X(), label_coord.Y(), label_size ); Debug( 1, "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, EAF = %d, FRI = %d, RBP = %d, ARBP = %d, FM = %d", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion ); - if ( purpose == ANALYSIS ) - { + //Set video recording flag for event start constructor and easy reference in code + videoRecording = ((GetOptVideoWriter() == H264PASSTHROUGH) && camera->SupportsNativeVideo()); + + if ( purpose == ANALYSIS ) { static char path[PATH_MAX]; strncpy( path, config.dir_events, sizeof(path) ); @@ -469,10 +455,8 @@ Monitor::Monitor( struct stat statbuf; errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't make %s: %s", path, strerror(errno)); } } @@ -481,10 +465,8 @@ Monitor::Monitor( errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't make %s: %s", path, strerror(errno)); } char temp_path[PATH_MAX]; @@ -498,8 +480,7 @@ Monitor::Monitor( } while( shared_data->last_write_index == (unsigned int)image_buffer_count - && shared_data->last_write_time == 0) - { + && shared_data->last_write_time == 0) { Warning( "Waiting for capture daemon" ); sleep( 1 ); } @@ -558,46 +539,42 @@ bool Monitor::connect() { exit( -1 ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < (void *)0 ) { Error( "Can't shmat: %s", strerror(errno)); exit( -1 ); } #endif // ZM_MEM_MAPPED shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - struct timeval *shared_timestamps = (struct timeval *)((char *)trigger_data + sizeof(TriggerData)); + video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); + struct timeval *shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); - if(((unsigned long)shared_images % 16) != 0) { - /* Align images buffer to nearest 16 byte boundary */ - Debug(3,"Aligning shared memory images to the next 16 byte boundary"); - shared_images = (uint8_t*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16))); + if(((unsigned long)shared_images % 64) != 0) { + /* Align images buffer to nearest 64 byte boundary */ + Debug(3,"Aligning shared memory images to the next 64 byte boundary"); + shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); } image_buffer = new Snapshot[image_buffer_count]; - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { image_buffer[i].timestamp = &(shared_timestamps[i]); image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) ); image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { /* Four field motion adaptive deinterlacing in use */ /* Allocate a buffer for the next image */ next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); next_buffer.timestamp = new struct timeval; } - if ( ( purpose == ANALYSIS ) && analysis_fps ) - { + if ( ( purpose == ANALYSIS ) && analysis_fps ) { // Size of pre event buffer must be greater than pre_event_count // if alarm_frame_count > 1, because in this case the buffer contains // alarmed images that must be discarded when event is created pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; pre_event_buffer = new Snapshot[pre_event_buffer_count]; - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { pre_event_buffer[i].timestamp = new struct timeval; pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); } @@ -606,8 +583,7 @@ bool Monitor::connect() { return true; } -Monitor::~Monitor() -{ +Monitor::~Monitor() { if ( timestamps ) { delete[] timestamps; timestamps = 0; @@ -616,29 +592,27 @@ Monitor::~Monitor() delete[] images; images = 0; } - if ( privacy_bitmask ) { - delete[] privacy_bitmask; - privacy_bitmask = NULL; - } + if ( privacy_bitmask ) { + delete[] privacy_bitmask; + privacy_bitmask = NULL; + } if ( mem_ptr ) { - if ( event ) + if ( event ) { Info( "%s: %03d - Closing event %d, shutting down", name, image_count, event->Id() ); - closeEvent(); + closeEvent(); + } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { delete next_buffer.image; delete next_buffer.timestamp; } - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { delete image_buffer[i].image; } delete[] image_buffer; } // end if mem_ptr - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; @@ -646,24 +620,19 @@ Monitor::~Monitor() delete camera; if ( mem_ptr ) { - if ( purpose == ANALYSIS ) - { + if ( purpose == ANALYSIS ) { shared_data->state = state = IDLE; shared_data->last_read_index = image_buffer_count; shared_data->last_read_time = 0; - if ( analysis_fps ) - { - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { delete pre_event_buffer[i].image; delete pre_event_buffer[i].timestamp; } delete[] pre_event_buffer; } - } - else if ( purpose == CAPTURE ) - { + } else if ( purpose == CAPTURE ) { shared_data->valid = false; memset( mem_ptr, 0, mem_size ); } @@ -676,12 +645,12 @@ Monitor::~Monitor() close( map_fd ); if ( purpose == CAPTURE ) { - char mmap_path[PATH_MAX] = ""; - snprintf( mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", config.path_map, id ); + char mmap_path[PATH_MAX] = ""; + snprintf( mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", config.path_map, id ); - if ( unlink( mmap_path ) < 0 ) { - Warning( "Can't unlink '%s': %s", mmap_path, strerror(errno) ); - } + if ( unlink( mmap_path ) < 0 ) { + Warning( "Can't unlink '%s': %s", mmap_path, strerror(errno) ); + } } #else // ZM_MEM_MAPPED struct shmid_ds shm_data; @@ -699,8 +668,7 @@ Monitor::~Monitor() } // end if mem_ptr } -void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) -{ +void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) { for ( int i = 0; i < n_zones; i++ ) delete zones[i]; delete[] zones; @@ -708,19 +676,15 @@ void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) zones = p_zones; } -void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) -{ +void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) { if ( privacy_bitmask ) delete[] privacy_bitmask; privacy_bitmask = NULL; Image *privacy_image = NULL; - for ( int i = 0; i < n_zones; i++ ) - { - if ( p_zones[i]->IsPrivacy() ) - { - if ( !privacy_image ) - { + for ( int i = 0; i < n_zones; i++ ) { + if ( p_zones[i]->IsPrivacy() ) { + if ( !privacy_image ) { privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE); privacy_image->Clear(); } @@ -732,20 +696,16 @@ void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) privacy_bitmask = privacy_image->Buffer(); } -Monitor::State Monitor::GetState() const -{ +Monitor::State Monitor::GetState() const { return( (State)shared_data->state ); } -int Monitor::GetImage( int index, int scale ) -{ - if ( index < 0 || index > image_buffer_count ) - { +int Monitor::GetImage( int index, int scale ) { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Image *image; // If we are going to be modifying the snapshot before writing, then we need to copy it if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { @@ -772,75 +732,59 @@ int Monitor::GetImage( int index, int scale ) static char filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); image->WriteJpeg( filename ); - } - else - { + } else { Error( "Unable to generate image, no images in buffer" ); } return( 0 ); } -struct timeval Monitor::GetTimestamp( int index ) const -{ - if ( index < 0 || index > image_buffer_count ) - { +struct timeval Monitor::GetTimestamp( int index ) const { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Snapshot *snap = &image_buffer[index]; return( *(snap->timestamp) ); - } - else - { + } else { static struct timeval null_tv = { 0, 0 }; return( null_tv ); } } -unsigned int Monitor::GetLastReadIndex() const -{ +unsigned int Monitor::GetLastReadIndex() const { return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 ); } -unsigned int Monitor::GetLastWriteIndex() const -{ +unsigned int Monitor::GetLastWriteIndex() const { return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 ); } -unsigned int Monitor::GetLastEvent() const -{ +unsigned int Monitor::GetLastEvent() const { return( shared_data->last_event ); } -double Monitor::GetFPS() const -{ +double Monitor::GetFPS() const { int index1 = shared_data->last_write_index; - if ( index1 == image_buffer_count ) - { + if ( index1 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) - { + if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) { return( 0.0 ); } struct timeval time1 = *snap1->timestamp; int image_count = image_buffer_count; int index2 = (index1+1)%image_buffer_count; - if ( index2 == image_buffer_count ) - { + if ( index2 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap2 = &image_buffer[index2]; - while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) - { - if ( index1 == index2 ) - { + while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) { + if ( index1 == index2 ) { return( 0.0 ); } index2 = (index2+1)%image_buffer_count; @@ -853,143 +797,110 @@ double Monitor::GetFPS() const double curr_fps = image_count/time_diff; - if ( curr_fps < 0.0 ) - { + if ( curr_fps < 0.0 ) { //Error( "Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count ); return( 0.0 ); } return( curr_fps ); } -useconds_t Monitor::GetAnalysisRate() -{ +useconds_t Monitor::GetAnalysisRate() { double capturing_fps = GetFPS(); - if ( !analysis_fps ) - { + if ( !analysis_fps ) { return( 0 ); - } - else if ( analysis_fps > capturing_fps ) - { + } else if ( analysis_fps > capturing_fps ) { Warning( "Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps ); return( 0 ); - } - else - { + } else { return( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) ); } } -void Monitor::UpdateAdaptiveSkip() -{ - if ( config.opt_adaptive_skip ) - { +void Monitor::UpdateAdaptiveSkip() { + if ( config.opt_adaptive_skip ) { double capturing_fps = GetFPS(); - if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) - { + if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) { Info( "Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps ); adaptive_skip = false; - } - else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) - { + } else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) { Info( "Enabling adaptive skip feature" ); adaptive_skip = true; } - } - else - { + } else { adaptive_skip = false; } } -void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) -{ +void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) { trigger_data->trigger_state = TRIGGER_ON; trigger_data->trigger_score = force_score; strncpy( trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause) ); strncpy( trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text) ); } -void Monitor::ForceAlarmOff() -{ +void Monitor::ForceAlarmOff() { trigger_data->trigger_state = TRIGGER_OFF; } -void Monitor::CancelForced() -{ +void Monitor::CancelForced() { trigger_data->trigger_state = TRIGGER_CANCEL; } -void Monitor::actionReload() -{ +void Monitor::actionReload() { shared_data->action |= RELOAD; } -void Monitor::actionEnable() -{ +void Monitor::actionEnable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 1 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionDisable() -{ +void Monitor::actionDisable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 0 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionSuspend() -{ +void Monitor::actionSuspend() { shared_data->action |= SUSPEND; } -void Monitor::actionResume() -{ +void Monitor::actionResume() { shared_data->action |= RESUME; } -int Monitor::actionBrightness( int p_brightness ) -{ - if ( purpose != CAPTURE ) - { - if ( p_brightness >= 0 ) - { +int Monitor::actionBrightness( int p_brightness ) { + if ( purpose != CAPTURE ) { + if ( p_brightness >= 0 ) { shared_data->brightness = p_brightness; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set brightness" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get brightness" ); return( -1 ); } @@ -1000,36 +911,27 @@ int Monitor::actionBrightness( int p_brightness ) return( camera->Brightness( p_brightness ) ); } -int Monitor::actionContrast( int p_contrast ) -{ - if ( purpose != CAPTURE ) - { - if ( p_contrast >= 0 ) - { +int Monitor::actionContrast( int p_contrast ) { + if ( purpose != CAPTURE ) { + if ( p_contrast >= 0 ) { shared_data->contrast = p_contrast; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set contrast" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get contrast" ); return( -1 ); } @@ -1040,36 +942,27 @@ int Monitor::actionContrast( int p_contrast ) return( camera->Contrast( p_contrast ) ); } -int Monitor::actionHue( int p_hue ) -{ - if ( purpose != CAPTURE ) - { - if ( p_hue >= 0 ) - { +int Monitor::actionHue( int p_hue ) { + if ( purpose != CAPTURE ) { + if ( p_hue >= 0 ) { shared_data->hue = p_hue; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set hue" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get hue" ); return( -1 ); } @@ -1080,36 +973,27 @@ int Monitor::actionHue( int p_hue ) return( camera->Hue( p_hue ) ); } -int Monitor::actionColour( int p_colour ) -{ - if ( purpose != CAPTURE ) - { - if ( p_colour >= 0 ) - { +int Monitor::actionColour( int p_colour ) { + if ( purpose != CAPTURE ) { + if ( p_colour >= 0 ) { shared_data->colour = p_colour; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set colour" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get colour" ); return( -1 ); } @@ -1120,16 +1004,13 @@ int Monitor::actionColour( int p_colour ) return( camera->Colour( p_colour ) ); } -void Monitor::DumpZoneImage( const char *zone_string ) -{ +void Monitor::DumpZoneImage( const char *zone_string ) { int exclude_id = 0; int extra_colour = 0; Polygon extra_zone; - if ( zone_string ) - { - if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) - { + if ( zone_string ) { + if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) { Error( "Failed to parse zone string, ignoring" ); } } @@ -1143,36 +1024,23 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB ); } - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id ) continue; Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) - { + if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) { colour = extra_colour; - } - else - { - if ( zones[i]->IsActive() ) - { + } else { + if ( zones[i]->IsActive() ) { colour = RGB_RED; - } - else if ( zones[i]->IsInclusive() ) - { + } else if ( zones[i]->IsInclusive() ) { colour = RGB_ORANGE; - } - else if ( zones[i]->IsExclusive() ) - { + } else if ( zones[i]->IsExclusive() ) { colour = RGB_PURPLE; - } - else if ( zones[i]->IsPreclusive() ) - { + } else if ( zones[i]->IsPreclusive() ) { colour = RGB_BLUE; - } - else - { + } else { colour = RGB_WHITE; } } @@ -1180,8 +1048,7 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.Outline( colour, zones[i]->GetPolygon() ); } - if ( extra_zone.getNumCoords() ) - { + if ( extra_zone.getNumCoords() ) { zone_image.Fill( extra_colour, 2, extra_zone ); zone_image.Outline( extra_colour, extra_zone ); } @@ -1191,10 +1058,8 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.WriteJpeg( filename ); } -void Monitor::DumpImage( Image *dump_image ) const -{ - if ( image_count && !(image_count%10) ) - { +void Monitor::DumpImage( Image *dump_image ) const { + if ( image_count && !(image_count%10) ) { static char filename[PATH_MAX]; static char new_filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); @@ -1204,8 +1069,7 @@ void Monitor::DumpImage( Image *dump_image ) const } } -bool Monitor::CheckSignal( const Image *image ) -{ +bool Monitor::CheckSignal( const Image *image ) { static bool static_undef = true; /* RGB24 colors */ static uint8_t red_val; @@ -1215,10 +1079,8 @@ bool Monitor::CheckSignal( const Image *image ) static Rgb colour_val; /* RGB32 color */ static int usedsubpixorder; - if ( config.signal_check_points > 0 ) - { - if ( static_undef ) - { + if ( config.signal_check_points > 0 ) { + if ( static_undef ) { static_undef = false; usedsubpixorder = camera->SubpixelOrder(); colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ @@ -1235,10 +1097,8 @@ bool Monitor::CheckSignal( const Image *image ) int colours = image->Colours(); int index = 0; - for ( int i = 0; i < config.signal_check_points; i++ ) - { - while( true ) - { + for ( int i = 0; i < config.signal_check_points; i++ ) { + while( true ) { index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); if ( !config.timestamp_on_capture || !label_format[0] ) break; @@ -1247,65 +1107,63 @@ bool Monitor::CheckSignal( const Image *image ) break; } - if(colours == ZM_COLOUR_GRAY8) { - if ( *(buffer+index) != grayscale_val ) - return true; - - } else if(colours == ZM_COLOUR_RGB24) { - const uint8_t *ptr = buffer+(index*colours); - - if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { - if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) - return true; - } else { - /* Assume RGB */ - if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + if(colours == ZM_COLOUR_GRAY8) { + if ( *(buffer+index) != grayscale_val ) return true; + + } else if(colours == ZM_COLOUR_RGB24) { + const uint8_t *ptr = buffer+(index*colours); + + if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { + if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) + return true; + } else { + /* Assume RGB */ + if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + return true; + } + + } else if(colours == ZM_COLOUR_RGB32) { + if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { + if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) + return true; + } else { + /* Assume RGBA or BGRA */ + if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) + return true; + } } - - } else if(colours == ZM_COLOUR_RGB32) { - if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { - if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) - return true; - } else { - /* Assume RGBA or BGRA */ - if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) - return true; - } - } - + } return( false ); } return( true ); } -bool Monitor::Analyse() -{ - if ( shared_data->last_read_index == shared_data->last_write_index ) - { +bool Monitor::Analyse() { + if ( shared_data->last_read_index == shared_data->last_write_index ) { + // I wonder how often this happens. Maybe if this happens we should sleep or something? return( false ); } struct timeval now; gettimeofday( &now, NULL ); - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { fps = double(fps_report_interval)/(now.tv_sec-last_fps_time); Info( "%s: %d - Analysing at %.2f fps", name, image_count, fps ); last_fps_time = now.tv_sec; } int index; - if ( adaptive_skip ) - { + if ( adaptive_skip ) { int read_margin = shared_data->last_read_index - shared_data->last_write_index; if ( read_margin < 0 ) read_margin += image_buffer_count; int step = 1; - if ( read_margin > 0 ) - { + // Isn't read_margin always > 0 here? + if ( read_margin > 0 ) { + // TODO explain this so... 90% of image buffer / 50% of read margin? step = (9*image_buffer_count)/(5*read_margin); } @@ -1313,21 +1171,15 @@ bool Monitor::Analyse() if ( pending_frames < 0 ) pending_frames += image_buffer_count; Debug( 4, "RI:%d, WI: %d, PF = %d, RM = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); - if ( step <= pending_frames ) - { + if ( step <= pending_frames ) { index = (shared_data->last_read_index+step)%image_buffer_count; - } - else - { - if ( pending_frames ) - { + } else { + if ( pending_frames ) { Warning( "Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size" ); } index = shared_data->last_write_index%image_buffer_count; } - } - else - { + } else { index = shared_data->last_write_index%image_buffer_count; } @@ -1335,32 +1187,25 @@ bool Monitor::Analyse() struct timeval *timestamp = snap->timestamp; Image *snap_image = snap->image; - if ( shared_data->action ) - { - if ( shared_data->action & RELOAD ) - { + if ( shared_data->action ) { + if ( shared_data->action & RELOAD ) { Info( "Received reload indication at count %d", image_count ); shared_data->action &= ~RELOAD; Reload(); } - if ( shared_data->action & SUSPEND ) - { - if ( Active() ) - { + if ( shared_data->action & SUSPEND ) { + if ( Active() ) { Info( "Received suspend indication at count %d", image_count ); shared_data->active = false; //closeEvent(); } - if ( config.max_suspend_time ) - { + if ( config.max_suspend_time ) { auto_resume_time = now.tv_sec + config.max_suspend_time; } shared_data->action &= ~SUSPEND; } - if ( shared_data->action & RESUME ) - { - if ( Enabled() && !Active() ) - { + if ( shared_data->action & RESUME ) { + if ( Enabled() && !Active() ) { Info( "Received resume indication at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1369,9 +1214,9 @@ bool Monitor::Analyse() } shared_data->action &= ~RESUME; } - } - if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) - { + } // end if shared_data->action + + if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { Info( "Auto resuming at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1383,31 +1228,27 @@ bool Monitor::Analyse() static int last_section_mod = 0; static bool last_signal; - if ( static_undef ) - { + if ( static_undef ) { +// Sure would be nice to be able to assume that these were already initialized. It's just 1 compare/branch, but really not neccessary. static_undef = false; timestamps = new struct timeval *[pre_event_count]; images = new Image *[pre_event_count]; last_signal = shared_data->signal; } - if ( Enabled() ) - { + if ( Enabled() ) { bool signal = shared_data->signal; bool signal_change = (signal != last_signal); - if ( trigger_data->trigger_state != TRIGGER_OFF ) - { + + if ( trigger_data->trigger_state != TRIGGER_OFF ) { unsigned int score = 0; - if ( Ready() ) - { + if ( Ready() ) { std::string cause; Event::StringSetMap noteSetMap; - if ( trigger_data->trigger_state == TRIGGER_ON ) - { + if ( trigger_data->trigger_state == TRIGGER_ON ) { score += trigger_data->trigger_score; - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += trigger_data->trigger_cause; @@ -1416,25 +1257,21 @@ bool Monitor::Analyse() noteSet.insert( trigger_data->trigger_text ); noteSetMap[trigger_data->trigger_cause] = noteSet; } - if ( signal_change ) - { + if ( signal_change ) { const char *signalText; - if ( !signal ) + if ( !signal ) { signalText = "Lost"; - else - { + } else { signalText = "Reacquired"; score += 100; } Warning( "%s: %s", SIGNAL_CAUSE, signalText ); - if ( event && !signal ) - { + if ( event && !signal ) { Info( "%s: %03d - Closing event %d, signal loss", name, image_count, event->Id() ); closeEvent(); last_section_mod = 0; } - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += SIGNAL_CAUSE; @@ -1445,28 +1282,22 @@ bool Monitor::Analyse() shared_data->state = state = IDLE; shared_data->active = signal; ref_image = *snap_image; - } - else if ( signal && Active() && (function == MODECT || function == MOCORD) ) - { + + } else if ( signal && Active() && (function == MODECT || function == MOCORD) ) { Event::StringSet zoneSet; int motion_score = last_motion_score; - if ( !(image_count % (motion_frame_skip+1) ) ) - { + if ( !(image_count % (motion_frame_skip+1) ) ) { // Get new score. motion_score = last_motion_score = DetectMotion( *snap_image, zoneSet ); } //int motion_score = DetectBlack( *snap_image, zoneSet ); - if ( motion_score ) - { - if ( !event ) - { + if ( motion_score ) { + if ( !event ) { score += motion_score; if ( cause.length() ) cause += ", "; cause += MOTION_CAUSE; - } - else - { + } else { score += motion_score; } noteSetMap[MOTION_CAUSE] = zoneSet; @@ -1474,20 +1305,14 @@ bool Monitor::Analyse() } shared_data->active = signal; } - if ( (!signal_change && signal) && n_linked_monitors > 0 ) - { + if ( (!signal_change && signal) && n_linked_monitors > 0 ) { bool first_link = true; Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) - { - if ( linked_monitors[i]->isConnected() ) - { - if ( linked_monitors[i]->hasAlarmed() ) - { - if ( !event ) - { - if ( first_link ) - { + for ( int i = 0; i < n_linked_monitors; i++ ) { + if ( linked_monitors[i]->isConnected() ) { + if ( linked_monitors[i]->hasAlarmed() ) { + if ( !event ) { + if ( first_link ) { if ( cause.length() ) cause += ", "; cause += LINKED_CAUSE; @@ -1497,146 +1322,125 @@ bool Monitor::Analyse() noteSet.insert( linked_monitors[i]->Name() ); score += 50; } - } - else - { + } else { linked_monitors[i]->connect(); } } if ( noteSet.size() > 0 ) noteSetMap[LINKED_CAUSE] = noteSet; } - if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) - { - if ( event ) - { + //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? + if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { + if ( event ) { + //TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); int section_mod = timestamp->tv_sec%section_length; - if ( section_mod < last_section_mod ) - { - if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) - { - if ( state == TAPE ) - { + if ( section_mod < last_section_mod ) { + if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { + if ( state == TAPE ) { shared_data->state = state = IDLE; Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() ) - } - else + } else { Info( "%s: %03d - Closing event %d, section end forced ", name, image_count, event->Id() ); + } closeEvent(); last_section_mod = 0; } - } - else - { + } else { last_section_mod = section_mod; } } - if ( !event ) - { + if ( !event ) { // Create event - event = new Event( this, *timestamp, "Continuous", noteSetMap ); + event = new Event( this, *timestamp, "Continuous", noteSetMap, videoRecording ); shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() ); /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) - { + if ( state == IDLE ) { shared_data->state = state = TAPE; } //if ( config.overlap_timed_events ) - if ( false ) - { + if ( false ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } } - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } + } event->AddFrames( pre_event_images, images, timestamps ); } - } - } + } // end if false or config.overlap_timed_events + } // end if ! event } - if ( score ) - { - if ( (state == IDLE || state == TAPE || state == PREALARM ) ) - { - if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) - { + if ( score ) { + if ( (state == IDLE || state == TAPE || state == PREALARM ) ) { + if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) { Info( "%s: %03d - Gone into alarm state", name, image_count ); shared_data->state = state = ALARM; - if ( signal_change || (function != MOCORD && state != ALERT) ) - { + if ( signal_change || (function != MOCORD && state != ALERT) ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } event = new Event( this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap ); - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer if ( alarm_frame_count > 1 ) @@ -1646,8 +1450,7 @@ bool Monitor::Analyse() // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; @@ -1656,132 +1459,100 @@ bool Monitor::Analyse() event = new Event( this, *(image_buffer[pre_index].timestamp), cause, noteSetMap ); } shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() ); - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } + } event->AddFrames( pre_event_images, images, timestamps ); } - if ( alarm_frame_count ) - { + if ( alarm_frame_count ) { event->SavePreAlarmFrames(); } } - } - else if ( state != PREALARM ) - { + } else if ( state != PREALARM ) { Info( "%s: %03d - Gone into prealarm state", name, image_count ); shared_data->state = state = PREALARM; } - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { Info( "%s: %03d - Gone back into alarm state", name, image_count ); shared_data->state = state = ALARM; } last_alarm_count = image_count; - } - else - { - if ( state == ALARM ) - { + } else { + if ( state == ALARM ) { Info( "%s: %03d - Gone into alert state", name, image_count ); shared_data->state = state = ALERT; - } - else if ( state == ALERT ) - { - if ( image_count-last_alarm_count > post_event_count ) - { + } else if ( state == ALERT ) { + if ( image_count-last_alarm_count > post_event_count ) { Info( "%s: %03d - Left alarm state (%d) - %d(%d) images", name, image_count, event->Id(), event->Frames(), event->AlarmFrames() ); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) - if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) - { + if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) { shared_data->state = state = IDLE; Info( "%s: %03d - Closing event %d, alarm end%s", name, image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); closeEvent(); - } - else - { + } else { shared_data->state = state = TAPE; } } } - if ( state == PREALARM ) - { - if ( function != MOCORD ) - { + if ( state == PREALARM ) { + if ( function != MOCORD ) { shared_data->state = state = IDLE; - } - else - { + } else { shared_data->state = state = TAPE; } } if ( Event::PreAlarmCount() ) Event::EmptyPreAlarmFrames(); } - if ( state != IDLE ) - { - if ( state == PREALARM || state == ALARM ) - { - if ( config.create_analysis_images ) - { + if ( state != IDLE ) { + if ( state == PREALARM || state == ALARM ) { + if ( config.create_analysis_images ) { bool got_anal_image = false; alarm_image.Assign( *snap_image ); - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( zones[i]->AlarmImage() ) - { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( zones[i]->AlarmImage() ) { alarm_image.Overlay( *(zones[i]->AlarmImage()) ); got_anal_image = true; } - if ( config.record_event_stats && state == ALARM ) - { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } } - if ( got_anal_image ) - { + if ( got_anal_image ) { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score, &alarm_image ); else event->AddFrame( snap_image, *timestamp, score, &alarm_image ); - } - else - { + } else { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score ); else event->AddFrame( snap_image, *timestamp, score ); } - } - else - { - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( config.record_event_stats && state == ALARM ) - { + } else { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } @@ -1793,42 +1564,38 @@ bool Monitor::Analyse() } if ( event && noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { event->AddFrame( snap_image, *timestamp ); if ( noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == TAPE ) - { - if ( !(image_count%(frame_skip+1)) ) - { - if ( config.bulk_frame_interval > 1 ) - { + } else if ( state == TAPE ) { + //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly + //if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { + // I don't think this is required, and causes problems, as the event file hasn't been setup yet. + //Warning("In state TAPE, + //video_store_data->recording = event->StartTime(); + //} + + if ( !(image_count%(frame_skip+1)) ) { + if ( config.bulk_frame_interval > 1 ) { event->AddFrame( snap_image, *timestamp, (event->Frames()AddFrame( snap_image, *timestamp ); } } } - } + } // end if ! IDLE } - } - else - { - if ( event ) - { + } else { + if ( event ) { Info( "%s: %03d - Closing event %d, trigger off", name, image_count, event->Id() ); closeEvent(); } shared_data->state = state = IDLE; last_section_mod = 0; - } - if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) - { + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) + + if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { if ( state == ALARM ) { ref_image.Blend( *snap_image, alarm_ref_blend_perc ); } else { @@ -1836,14 +1603,13 @@ bool Monitor::Analyse() } } last_signal = signal; - } + } // end if Enabled() shared_data->last_read_index = index%image_buffer_count; //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec; shared_data->last_read_time = now.tv_sec; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, add analysed image to dedicated pre event buffer int pre_index = image_count%pre_event_buffer_count; pre_event_buffer[pre_index].image->Assign(*snap->image); @@ -1855,8 +1621,7 @@ bool Monitor::Analyse() return( true ); } -void Monitor::Reload() -{ +void Monitor::Reload() { Debug( 1, "Reloading monitor %s", name ); if ( event ) @@ -1865,29 +1630,26 @@ void Monitor::Reload() closeEvent(); static char sql[ZM_SQL_MED_BUFSIZ]; + // This seems to have fallen out of date. snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors != 1 ) - { + if ( n_monitors != 1 ) { Error( "Bogus number of monitors, %d, returned. Can't reload", n_monitors ); return; } - if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) - { + if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) { int index = 0; function = (Function)atoi(dbrow[index++]); enabled = atoi(dbrow[index++]); @@ -1927,8 +1689,7 @@ void Monitor::Reload() ReloadLinkedMonitors( p_linked_monitors ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -1937,11 +1698,9 @@ void Monitor::Reload() ReloadZones(); } -void Monitor::ReloadZones() -{ +void Monitor::ReloadZones() { Debug( 1, "Reloading zones for monitor %s", name ); - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; @@ -1950,13 +1709,10 @@ void Monitor::ReloadZones() //DumpZoneImage(); } -void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) -{ +void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) { Debug( 1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors ); - if ( n_linked_monitors ) - { - for( int i = 0; i < n_linked_monitors; i++ ) - { + if ( n_linked_monitors ) { + for( int i = 0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; @@ -1964,44 +1720,35 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } n_linked_monitors = 0; - if ( p_linked_monitors ) - { + if ( p_linked_monitors ) { int n_link_ids = 0; unsigned int link_ids[256]; char link_id_str[8]; char *dest_ptr = link_id_str; const char *src_ptr = p_linked_monitors; - while( 1 ) - { + while( 1 ) { dest_ptr = link_id_str; - while( *src_ptr >= '0' && *src_ptr <= '9' ) - { - if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) - { + while( *src_ptr >= '0' && *src_ptr <= '9' ) { + if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { *dest_ptr++ = *src_ptr++; - } - else - { + } else { break; } } // Add the link monitor - if ( dest_ptr != link_id_str ) - { + if ( dest_ptr != link_id_str ) { *dest_ptr = '\0'; unsigned int link_id = atoi(link_id_str); - if ( link_id > 0 && link_id != id) - { + if ( link_id > 0 && link_id != id) { Debug( 3, "Found linked monitor id %d", link_id ); int j; - for ( j = 0; j < n_link_ids; j++ ) - { + for ( j = 0; j < n_link_ids; j++ ) { if ( link_ids[j] == link_id ) break; } - if ( j == n_link_ids ) // Not already found - { + if ( j == n_link_ids ) { + // Not already found link_ids[n_link_ids++] = link_id; } } @@ -2013,38 +1760,31 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) if ( !*src_ptr ) break; } - if ( n_link_ids > 0 ) - { + if ( n_link_ids > 0 ) { Debug( 1, "Linking to %d monitors", n_link_ids ); linked_monitors = new MonitorLink *[n_link_ids]; int count = 0; - for ( int i = 0; i < n_link_ids; i++ ) - { + for ( int i = 0; i < n_link_ids; i++ ) { Debug( 1, "Checking linked monitor %d", link_ids[i] ); static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id, Name from Monitors where Id = %d and Function != 'None' and Function != 'Monitor' and Enabled = 1", link_ids[i] ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors == 1 ) - { + if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row( result ); Debug( 1, "Linking to monitor %d", link_ids[i] ); linked_monitors[count++] = new MonitorLink( link_ids[i], dbrow[1] ); - } - else - { + } else { Warning( "Can't link to monitor %d, invalid id, function or not enabled", link_ids[i] ); } mysql_free_result( result ); @@ -2055,9 +1795,8 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } #if ZM_HAS_V4L -int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; +int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; if ( device[0] ) { sql += " AND Device='"; sql += device; @@ -2077,8 +1816,7 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2117,13 +1855,19 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2174,6 +1918,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); @@ -2187,6 +1932,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2214,14 +1963,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2232,9 +1981,8 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); } #endif // ZM_HAS_V4L -int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; +int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; if ( staticConfig.SERVER_ID ) { sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID ); } @@ -2253,8 +2001,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2264,11 +2011,11 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c int enabled = atoi(dbrow[col]); col++; const char *linked_monitors = dbrow[col]; col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2276,14 +2023,19 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2309,8 +2061,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c bool embed_exif = (*dbrow[col] != '0'); col++; Camera *camera = 0; - if ( protocol == "http" ) - { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method, @@ -2324,12 +2075,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #if HAVE_LIBAVFORMAT - else if ( protocol == "rtsp" ) - { + else if ( protocol == "rtsp" ) { camera = new RemoteCameraRtsp( id, method, @@ -2344,12 +2095,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #endif // HAVE_LIBAVFORMAT - else - { + else { Fatal( "Unexpected remote camera protocol '%s'", protocol.c_str() ); } @@ -2363,8 +2114,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c camera, orientation, deinterlacing, - event_prefix.c_str(), - label_format.c_str(), + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix, + label_format, Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2391,14 +2146,14 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name.c_str(), n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2408,9 +2163,8 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c return( n_monitors ); } -int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; +int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; if ( file[0] ) { sql += " AND Path='"; sql += file; @@ -2421,8 +2175,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } Debug( 1, "Loading File Monitors with %s", sql.c_str() ); MYSQL_RES *result = zmDbFetch( sql.c_str() ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2430,8 +2183,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2449,14 +2201,20 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col]; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; - + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; + int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; int label_size = atoi(dbrow[col]); col++; @@ -2490,7 +2248,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( @@ -2503,6 +2262,10 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2530,14 +2293,14 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2548,9 +2311,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } #if HAVE_LIBAVFORMAT -int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; +int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; if ( file[0] ) { sql += " AND Path = '"; sql += file; @@ -2570,8 +2332,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2579,11 +2340,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - const char *linked_monitors = dbrow[col]; col++; + const char *linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; const char *path = dbrow[col]; col++; const char *method = dbrow[col]; col++; - const char *options = dbrow[col]; col++; + const char *options = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2591,13 +2352,19 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2612,10 +2379,14 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose int section_length = atoi(dbrow[col]); col++; int frame_skip = atoi(dbrow[col]); col++; int motion_frame_skip = atoi(dbrow[col]); col++; + double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++; unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0); - int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; - int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; + double capture_fps = dbrow[col] ? atof(dbrow[col]) : 0;col++; + int capture_delay = capture_fps >0.0 ?int(DT_PREC_3/capture_fps):0; + double alarm_capture_fps = dbrow[col] ? atof(dbrow[col]) : 0; col++; + int alarm_capture_delay = alarm_capture_fps > 0.0 ?int(DT_PREC_3/alarm_capture_fps):0; + int fps_report_interval = atoi(dbrow[col]); col++; int ref_blend_perc = atoi(dbrow[col]); col++; int alarm_ref_blend_perc = atoi(dbrow[col]); col++; @@ -2634,7 +2405,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( @@ -2647,6 +2419,10 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2674,14 +2450,14 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2692,9 +2468,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose } #endif // HAVE_LIBAVFORMAT -Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) -{ - std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); +Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) { + std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ); if ( ! dbrow ) { @@ -2710,7 +2485,7 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) std::string type = dbrow[col]; col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - std::string linked_monitors = dbrow[col]; col++; + std::string linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; std::string device = dbrow[col]; col++; int channel = atoi(dbrow[col]); col++; @@ -2732,17 +2507,17 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) } else { v4l_captures_per_frame = config.captures_per_frame; } -Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); + Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; - std::string options = dbrow[col]; col++; - std::string user = dbrow[col]; col++; - std::string pass = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; + std::string options = dbrow[col] ? dbrow[col] : ""; col++; + std::string user = dbrow[col] ? dbrow[col] : ""; col++; + std::string pass = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2750,14 +2525,19 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + const char * event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char * label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2792,8 +2572,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int extras = (deinterlacing>>24)&0xff; Camera *camera = 0; - if ( type == "Local" ) - { + if ( type == "Local" ) { #if ZM_HAS_V4L camera = new LocalCamera( id, @@ -2812,16 +2591,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); #else // ZM_HAS_V4L Fatal( "You must have video4linux libraries and headers installed to use local analog or USB cameras for monitor %d", id ); #endif // ZM_HAS_V4L - } - else if ( type == "Remote" ) - { - if ( protocol == "http" ) - { + } else if ( type == "Remote" ) { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method.c_str(), @@ -2835,11 +2612,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( protocol == "rtsp" ) - { + } else if ( protocol == "rtsp" ) { #if HAVE_LIBAVFORMAT camera = new RemoteCameraRtsp( id, @@ -2855,19 +2631,16 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); #endif // HAVE_LIBAVFORMAT - } - else - { + } else { Fatal( "Unexpected remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); } - } - else if ( type == "File" ) - { + } else if ( type == "File" ) { camera = new FileCamera( id, path.c_str(), @@ -2878,11 +2651,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( type == "Ffmpeg" ) - { + } else if ( type == "Ffmpeg" ) { #if HAVE_LIBAVFORMAT camera = new FfmpegCamera( id, @@ -2896,14 +2668,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBAVFORMAT - } - else if (type == "Libvlc") - { + } else if (type == "Libvlc") { #if HAVE_LIBVLC camera = new LibvlcCamera( id, @@ -2917,14 +2688,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBVLC Fatal( "You must have vlc libraries installed to use vlc cameras for monitor %d", id ); #endif // HAVE_LIBVLC - } - else if ( type == "cURL" ) - { + } else if ( type == "cURL" ) { #if HAVE_LIBCURL camera = new cURLCamera( id, @@ -2938,14 +2708,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBCURL Fatal( "You must have libcurl installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBCURL - } - else - { + } else { Fatal( "Bogus monitor type '%s' for monitor %d", type.c_str(), id ); } monitor = new Monitor( @@ -2958,8 +2727,12 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); camera, orientation, deinterlacing, - event_prefix.c_str(), - label_format.c_str(), + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix, + label_format, Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2987,9 +2760,9 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); ); + camera->setMonitor( monitor ); int n_zones = 0; - if ( load_zones ) - { + if ( load_zones ) { Zone **zones = 0; n_zones = Zone::Load( monitor, zones ); monitor->AddZones( n_zones, zones ); @@ -2999,22 +2772,34 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); return( monitor ); } -int Monitor::Capture() -{ - static int FirstCapture = 1; +/* Returns 0 on success, even if no new images are available (transient error) + * Returns -1 on failure. + */ +int Monitor::Capture() { + static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image int captureResult; - int index = image_count%image_buffer_count; + unsigned int index = image_count%image_buffer_count; Image* capture_image = image_buffer[index].image; - if ( (deinterlacing & 0xff) == 4) { + unsigned int deinterlacing_value = deinterlacing & 0xff; + + if ( deinterlacing_value == 4 ) { if ( FirstCapture != 1 ) { /* Copy the next image into the shared memory */ capture_image->CopyBuffer(*(next_buffer.image)); } /* Capture a new next image */ - captureResult = camera->Capture(*(next_buffer.image)); + + //Check if FFMPEG camera + if ( ( videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { + captureResult = camera->CaptureAndRecord(*(next_buffer.image), + video_store_data->recording, + video_store_data->event_file ); + } else { + captureResult = camera->Capture(*(next_buffer.image)); + } if ( FirstCapture ) { FirstCapture = 0; @@ -3022,12 +2807,23 @@ int Monitor::Capture() } } else { - /* Capture directly into image buffer, avoiding the need to memcpy() */ - captureResult = camera->Capture(*capture_image); + //Check if FFMPEG camera + if ( (videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { + //Warning("ZMC: Recording: %d", video_store_data->recording); + captureResult = camera->CaptureAndRecord(*capture_image, video_store_data->recording, video_store_data->event_file); + }else{ + /* Capture directly into image buffer, avoiding the need to memcpy() */ + captureResult = camera->Capture(*capture_image); + } } - if ( captureResult != 0 ) - { + // CaptureAndRecord returns # of frames captured I think + if ( ( videowriter == H264PASSTHROUGH ) && ( captureResult > 0 ) ) { + //video_store_data->frameNumber = captureResult; + captureResult = 0; + } + + if ( captureResult != 0 ) { // Unable to capture image for temporary reason // Fake a signal loss image Rgb signalcolor; @@ -3038,27 +2834,24 @@ int Monitor::Capture() captureResult = 1; } - if ( captureResult == 1 ) - { + if ( captureResult == 1 ) { /* Deinterlacing */ - if ( (deinterlacing & 0xff) == 1 ) { + if ( deinterlacing_value == 1 ) { capture_image->Deinterlace_Discard(); - } else if ( (deinterlacing & 0xff) == 2 ) { + } else if ( deinterlacing_value == 2 ) { capture_image->Deinterlace_Linear(); - } else if ( (deinterlacing & 0xff) == 3 ) { + } else if ( deinterlacing_value == 3 ) { capture_image->Deinterlace_Blend(); - } else if ( (deinterlacing & 0xff) == 4 ) { + } else if ( deinterlacing_value == 4 ) { capture_image->Deinterlace_4Field( next_buffer.image, (deinterlacing>>8)&0xff ); - } else if ( (deinterlacing & 0xff) == 5 ) { + } else if ( deinterlacing_value == 5 ) { capture_image->Deinterlace_Blend_CustomRatio( (deinterlacing>>8)&0xff ); } - if ( orientation != ROTATE_0 ) - { - switch ( orientation ) - { + if ( orientation != ROTATE_0 ) { + switch ( orientation ) { case ROTATE_0 : { // No action required @@ -3080,23 +2873,17 @@ int Monitor::Capture() } } - } - if ( true ) { - - if ( capture_image->Size() > camera->ImageSize() ) - { + if ( capture_image->Size() > camera->ImageSize() ) { Error( "Captured image %d does not match expected size %d check width, height and colour depth",capture_image->Size(),camera->ImageSize() ); return( -1 ); } - if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) ) - { + if ( (index == shared_data->last_read_index) && (function > MONITOR) ) { Warning( "Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", index, image_count ); time_t now = time(0); double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec); time_t last_read_delta = now - shared_data->last_read_time; - if ( last_read_delta > (image_buffer_count/approxFps) ) - { + if ( last_read_delta > (image_buffer_count/approxFps) ) { Warning( "Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta ) shared_data->last_read_index = image_buffer_count; } @@ -3106,8 +2893,7 @@ int Monitor::Capture() capture_image->MaskPrivacy( privacy_bitmask ); gettimeofday( image_buffer[index].timestamp, NULL ); - if ( config.timestamp_on_capture ) - { + if ( config.timestamp_on_capture ) { TimestampImage( capture_image, image_buffer[index].timestamp ); } shared_data->signal = CheckSignal(capture_image); @@ -3116,8 +2902,7 @@ int Monitor::Capture() image_count++; - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { time_t now = image_buffer[index].timestamp->tv_sec; fps = double(fps_report_interval)/(now-last_fps_time); //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); @@ -3126,16 +2911,14 @@ int Monitor::Capture() last_fps_time = now; } - if ( shared_data->action & GET_SETTINGS ) - { + if ( shared_data->action & GET_SETTINGS ) { shared_data->brightness = camera->Brightness(); shared_data->hue = camera->Hue(); shared_data->colour = camera->Colour(); shared_data->contrast = camera->Contrast(); shared_data->action &= ~GET_SETTINGS; } - if ( shared_data->action & SET_SETTINGS ) - { + if ( shared_data->action & SET_SETTINGS ) { camera->Brightness( shared_data->brightness ); camera->Hue( shared_data->hue ); camera->Colour( shared_data->colour ); @@ -3143,15 +2926,13 @@ int Monitor::Capture() shared_data->action &= ~SET_SETTINGS; } return( 0 ); - } + } // end if captureResults == 1 shared_data->signal = false; return( -1 ); } -void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const -{ - if ( label_format[0] ) - { +void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const { + if ( label_format[0] ) { // Expand the strftime macros first char label_time_text[256]; strftime( label_time_text, sizeof(label_time_text), label_format, localtime( &ts_time->tv_sec ) ); @@ -3159,13 +2940,10 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; - while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) - { - if ( *s_ptr == '%' ) - { + while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) { + if ( *s_ptr == '%' ) { bool found_macro = false; - switch ( *(s_ptr+1) ) - { + switch ( *(s_ptr+1) ) { case 'N' : d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name ); found_macro = true; @@ -3179,8 +2957,7 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c found_macro = true; break; } - if ( found_macro ) - { + if ( found_macro ) { s_ptr += 2; continue; } @@ -3192,33 +2969,28 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c } } -bool Monitor::closeEvent() -{ - if ( event ) - { - if ( function == RECORD || function == MOCORD ) - { +bool Monitor::closeEvent() { + if ( event ) { + if ( function == RECORD || function == MOCORD ) { gettimeofday( &(event->EndTime()), NULL ); } delete event; + video_store_data->recording = (struct timeval){0}; event = 0; return( true ); } return( false ); } -unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) -{ +unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) { bool alarm = false; unsigned int score = 0; if ( n_zones <= 0 ) return( alarm ); - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", config.dir_events, id ); } ref_image.WriteJpeg( diag_path ); @@ -3226,25 +2998,21 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z ref_image.Delta( comp_image, &delta_image); - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", config.dir_events, id ); } delta_image.WriteJpeg( diag_path ); } // Blank out all exclusion zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; // need previous alarmed state for preclusive zone, so don't clear just yet if (!zone->IsPreclusive()) zone->ClearAlarm(); - if ( !zone->IsInactive() ) - { + if ( !zone->IsInactive() ) { continue; } Debug( 3, "Blanking inactive zone %s", zone->Label() ); @@ -3252,18 +3020,15 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } // Check preclusive zones first - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsPreclusive() ) - { + if ( !zone->IsPreclusive() ) { continue; } int old_zone_score = zone->Score(); bool old_zone_alarmed = zone->Alarmed(); Debug( 3, "Checking preclusive zone %s - old score: %d, state: %s", zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet" ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3279,7 +3044,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } if (zone->CheckExtendAlarmCount()) { alarm=true; - zone->SetAlarm(); + zone->SetAlarm(); } else { zone->ClearAlarm(); } @@ -3290,33 +3055,25 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z Coord alarm_centre; int top_score = -1; - if ( alarm ) - { + if ( alarm ) { alarm = false; score = 0; - } - else - { + } else { // Find all alarm pixels in active zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsActive() || zone->IsPreclusive()) - { + if ( !zone->IsActive() || zone->IsPreclusive()) { continue; } Debug( 3, "Checking active zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( (int)zone->Score() > top_score ) - { + if ( config.opt_control && track_motion ) { + if ( (int)zone->Score() > top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } @@ -3324,47 +3081,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } } - if ( alarm ) - { - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + if ( alarm ) { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsInclusive() ) - { + if ( !zone->IsInclusive() ) { continue; } Debug( 3, "Checking inclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( zone->Score() > (unsigned int)top_score ) - { + if ( config.opt_control && track_motion ) { + if ( zone->Score() > (unsigned int)top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } } } } - } - else - { + } else { // Find all alarm pixels in exclusive zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsExclusive() ) - { + if ( !zone->IsExclusive() ) { continue; } Debug( 3, "Checking exclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3375,15 +3121,12 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } } - if ( top_score > 0 ) - { + if ( top_score > 0 ) { shared_data->alarm_x = alarm_centre.X(); shared_data->alarm_y = alarm_centre.Y(); Info( "Got alarm centre at %d,%d, at count %d", shared_data->alarm_x, shared_data->alarm_y, image_count ); - } - else - { + } else { shared_data->alarm_x = shared_data->alarm_y = -1; } @@ -3391,44 +3134,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z return( score?score:alarm ); } -bool Monitor::DumpSettings( char *output, bool verbose ) -{ +bool Monitor::DumpSettings( char *output, bool verbose ) { output[0] = 0; sprintf( output+strlen(output), "Id : %d\n", id ); sprintf( output+strlen(output), "Name : %s\n", name ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() ); sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() ); sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() ); - } - else + } else #endif // ZM_HAS_V4L - if ( camera->IsRemote() ) - { + if ( camera->IsRemote() ) { sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() ); sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() ); sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() ); sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() ); - } - else if ( camera->IsFile() ) - { + } else if ( camera->IsFile() ) { sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() ); } #if HAVE_LIBAVFORMAT - else if ( camera->IsFfmpeg() ) - { + else if ( camera->IsFfmpeg() ) { sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() ); } #endif // HAVE_LIBAVFORMAT sprintf( output+strlen(output), "Width : %d\n", camera->Width() ); sprintf( output+strlen(output), "Height : %d\n", camera->Height() ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() ); } #endif // ZM_HAS_V4L @@ -3459,78 +3194,61 @@ bool Monitor::DumpSettings( char *output, bool verbose ) function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown" )))))); sprintf( output+strlen(output), "Zones : %d\n", n_zones ); - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { zones[i]->DumpSettings( output+strlen(output), verbose ); } return( true ); -} +} // bool Monitor::DumpSettings( char *output, bool verbose ) -bool MonitorStream::checkSwapPath( const char *path, bool create_path ) -{ +bool MonitorStream::checkSwapPath( const char *path, bool create_path ) { uid_t uid = getuid(); gid_t gid = getgid(); struct stat stat_buf; - if ( stat( path, &stat_buf ) < 0 ) - { - if ( create_path && errno == ENOENT ) - { + if ( stat( path, &stat_buf ) < 0 ) { + if ( create_path && errno == ENOENT ) { Debug( 3, "Swap path '%s' missing, creating", path ); - if ( mkdir( path, 0755 ) ) - { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); return( false ); } - if ( stat( path, &stat_buf ) < 0 ) - { + if ( stat( path, &stat_buf ) < 0 ) { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } - } - else - { + } else { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } } - if ( !S_ISDIR(stat_buf.st_mode) ) - { + if ( !S_ISDIR(stat_buf.st_mode) ) { Error( "Swap image path '%s' is not a directory", path ); return( false ); } mode_t mask = 0; - if ( uid == stat_buf.st_uid ) - { + if ( uid == stat_buf.st_uid ) { // If we are the owner mask = 00700; - } - else if ( gid == stat_buf.st_gid ) - { + } else if ( gid == stat_buf.st_gid ) { // If we are in the owner group mask = 00070; - } - else - { + } else { // We are neither the owner nor in the group mask = 00007; } - if ( (stat_buf.st_mode & mask) != mask ) - { + if ( (stat_buf.st_mode & mask) != mask ) { Error( "Insufficient permissions on swap image path '%s'", path ); return( false ); } return( true ); } -void MonitorStream::processCommand( const CmdMsg *msg ) -{ +void MonitorStream::processCommand( const CmdMsg *msg ) { Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) - { + switch( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : { Debug( 1, "Got PAUSE command" ); @@ -3792,8 +3510,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) status_msg.msg_type = MSG_DATA_WATCH; memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); int nbytes = 0; - if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) - { + if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -3808,8 +3525,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) updateFrameRate( monitor->GetFPS() ); } -bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); if ( type != STREAM_JPEG ) @@ -3817,25 +3533,19 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) if ( !config.timestamp_on_capture && timestamp ) send_raw = false; - if ( !send_raw ) - { + if ( !send_raw ) { Image temp_image( filepath ); return( sendFrame( &temp_image, timestamp ) ); - } - else - { + } else { int img_buffer_size = 0; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; FILE *fdj = NULL; - if ( (fdj = fopen( filepath, "r" )) ) - { + if ( (fdj = fopen( filepath, "r" )) ) { img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj ); fclose( fdj ); - } - else - { + } else { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } @@ -3847,8 +3557,7 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) fprintf( stdout, "--ZoneMinderFrame\r\n" ); fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { if ( !zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); @@ -3860,8 +3569,7 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 2; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } @@ -3873,17 +3581,14 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) return( false ); } -bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) { Image *send_image = prepareImage( image ); if ( !config.timestamp_on_capture && timestamp ) monitor->TimestampImage( send_image, timestamp ); #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { - if ( !vid_stream ) - { + if ( type == STREAM_MPEG ) { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); @@ -3894,8 +3599,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) base_time = *timestamp; DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 ); /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -3908,8 +3612,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameStartTime, NULL ); fprintf( stdout, "--ZoneMinderFrame\r\n" ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n" ); @@ -3930,8 +3633,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) break; } fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { if ( !zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); @@ -3943,8 +3645,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 1.5; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } @@ -3953,10 +3654,8 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) return( true ); } -void MonitorStream::runStream() -{ - if ( type == STREAM_SINGLE ) - { +void MonitorStream::runStream() { + if ( type == STREAM_SINGLE ) { // Not yet migrated over to stream class monitor->SingleImage( scale ); return; @@ -4033,28 +3732,23 @@ void MonitorStream::runStream() } float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) - while ( !zm_terminate ) - { + while ( !zm_terminate ) { bool got_command = false; - if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) - { + if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { break; } gettimeofday( &now, NULL ); - if ( connkey ) - { + if ( connkey ) { while(checkCommandQueue()) { got_command = true; } } //bool frame_sent = false; - if ( buffered_playback && delayed ) - { - if ( temp_read_index == temp_write_index ) - { + if ( buffered_playback && delayed ) { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Debug( 1, "Exceeded temporary streaming buffer" ); // Clear paused flag @@ -4062,34 +3756,26 @@ void MonitorStream::runStream() // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; - } - else - { - if ( !paused ) - { + } else { + if ( !paused ) { 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]; - if ( !swap_image->valid ) - { + if ( !swap_image->valid ) { paused = true; delayed = true; temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count ); - } - else - { + } else { //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // 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 ); - if ( temp_index%frame_mod == 0 ) - { + if ( temp_index%frame_mod == 0 ) { Debug( 2, "Sending delayed frame %d", temp_index ); // Send the next frame if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) @@ -4100,9 +3786,7 @@ void MonitorStream::runStream() temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count ); } } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; @@ -4113,14 +3797,11 @@ void MonitorStream::runStream() memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); //frame_sent = true; step = 0; - } - else - { + } else { int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( got_command || actual_delta_time > 5 ) - { + if ( got_command || actual_delta_time > 5 ) { // Send keepalive Debug( 2, "Sending keepalive frame %d", temp_index ); // Send the next frame @@ -4130,8 +3811,7 @@ void MonitorStream::runStream() } } } - if ( temp_read_index == temp_write_index ) - { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Warning( "Rewound over write index, resuming live play" ); // Clear paused flag @@ -4141,15 +3821,12 @@ void MonitorStream::runStream() replay_rate = ZM_RATE_BASE; } } - if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) - { + if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) { int index = monitor->shared_data->last_write_index%monitor->image_buffer_count; last_read_index = monitor->shared_data->last_write_index; //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ); - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) - { - if ( !paused && !delayed ) - { + if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { + if ( !paused && !delayed ) { // Send the next frame Monitor::Snapshot *snap = &monitor->image_buffer[index]; @@ -4161,24 +3838,19 @@ void MonitorStream::runStream() temp_read_index = temp_write_index; } } - if ( buffered_playback ) - { - if ( monitor->shared_data->valid ) - { - if ( monitor->image_buffer[index].timestamp->tv_sec ) - { + if ( buffered_playback ) { + if ( monitor->shared_data->valid ) { + if ( monitor->image_buffer[index].timestamp->tv_sec ) { int temp_index = temp_write_index%temp_image_buffer_count; 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, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index ); temp_image_buffer[temp_index].valid = true; } memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); 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 Warning( "Exceeded temporary buffer, resuming live play" ); // Clear paused flag @@ -4187,79 +3859,56 @@ void MonitorStream::runStream() delayed = false; replay_rate = ZM_RATE_BASE; } - } - else - { + } else { Warning( "Unable to store frame as timestamp invalid" ); } - } - else - { + } else { Warning( "Unable to store frame as shared memory invalid" ); } } frame_count++; } usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); - if ( ttl ) - { - if ( (now.tv_sec - stream_start_time) > ttl ) - { + if ( ttl ) { + if ( (now.tv_sec - stream_start_time) > ttl ) { break; } } - if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) - { + if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) { Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); break; } } - if ( buffered_playback ) - { + if ( buffered_playback ) { Debug( 1, "Cleaning swap files from %s", swap_path ); struct stat stat_buf; - if ( stat( swap_path, &stat_buf ) < 0 ) - { - if ( errno != ENOENT ) - { + if ( stat( swap_path, &stat_buf ) < 0 ) { + if ( errno != ENOENT ) { Error( "Can't stat '%s': %s", swap_path, 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 ); - } - else - { + } else { char glob_pattern[PATH_MAX] = ""; snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path ); glob_t pglob; int glob_status = glob( glob_pattern, 0, 0, &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) ); - } - else - { + } else { Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); } - } - else - { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) - { - if ( unlink( pglob.gl_pathv[i] ) < 0 ) - { + } else { + for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + if ( unlink( pglob.gl_pathv[i] ) < 0 ) { Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) ); } } } globfree( &pglob ); - if ( rmdir( swap_path ) < 0 ) - { + if ( rmdir( swap_path ) < 0 ) { Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) ); } } @@ -4268,8 +3917,7 @@ void MonitorStream::runStream() closeComms(); } -void Monitor::SingleImage( int scale) -{ +void Monitor::SingleImage( int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4277,14 +3925,12 @@ void Monitor::SingleImage( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); @@ -4294,21 +3940,18 @@ void Monitor::SingleImage( int scale) fwrite( img_buffer, img_buffer_size, 1, stdout ); } -void Monitor::SingleImageRaw( int scale) -{ +void Monitor::SingleImageRaw( int scale) { Image scaled_image; int index = shared_data->last_write_index%image_buffer_count; Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } @@ -4317,8 +3960,7 @@ void Monitor::SingleImageRaw( int scale) fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); } -void Monitor::SingleImageZip( int scale) -{ +void Monitor::SingleImageZip( int scale) { unsigned long img_buffer_size = 0; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4326,14 +3968,12 @@ void Monitor::SingleImageZip( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->Zip( img_buffer, &img_buffer_size ); @@ -4342,3 +3982,16 @@ void Monitor::SingleImageZip( int scale) fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); fwrite( img_buffer, img_buffer_size, 1, stdout ); } + +unsigned int Monitor::Colours() const { return( camera->Colours() ); } +unsigned int Monitor::SubpixelOrder() const { return( camera->SubpixelOrder() ); } +int Monitor::PrimeCapture() { + return( camera->PrimeCapture() ); +} +int Monitor::PreCapture() { + return( camera->PreCapture() ); +} +int Monitor::PostCapture() { + return( camera->PostCapture() ); +} +Monitor::Orientation Monitor::getOrientation() const { return orientation; } diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 7d33cc725..b5c3acfce 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -29,6 +29,7 @@ #include "zm_rgb.h" #include "zm_zone.h" #include "zm_event.h" +class Monitor; #include "zm_camera.h" #include "zm_utils.h" @@ -45,20 +46,17 @@ // This is the main class for monitors. Each monitor is associated // with a camera and is effectively a collector for events. // -class Monitor -{ -friend class MonitorStream; +class Monitor { + friend class MonitorStream; public: - typedef enum - { + typedef enum { QUERY=0, CAPTURE, ANALYSIS } Purpose; - typedef enum - { + typedef enum { NONE=1, MONITOR, MODECT, @@ -67,8 +65,7 @@ public: NODECT } Function; - typedef enum - { + typedef enum { ROTATE_0=1, ROTATE_90, ROTATE_180, @@ -77,8 +74,7 @@ public: FLIP_VERT } Orientation; - typedef enum - { + typedef enum { IDLE, PREALARM, ALARM, @@ -86,6 +82,12 @@ public: TAPE } State; + typedef enum { + DISABLED, + X264ENCODE, + H264PASSTHROUGH, + } VideoWriter; + protected: typedef std::set ZoneSet; @@ -94,8 +96,7 @@ protected: typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; /* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */ - typedef struct - { + typedef struct { uint32_t size; /* +0 */ uint32_t last_write_index; /* +4 */ uint32_t last_read_index; /* +8 */ @@ -120,12 +121,12 @@ protected: ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. */ union { /* +64 */ - time_t last_write_time; - uint64_t extrapad1; + time_t last_write_time; + uint64_t extrapad1; }; union { /* +72 */ - time_t last_read_time; - uint64_t extrapad2; + time_t last_read_time; + uint64_t extrapad2; }; uint8_t control_state[256]; /* +80 */ @@ -134,8 +135,7 @@ protected: typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState; /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */ - typedef struct - { + typedef struct { uint32_t size; uint32_t trigger_state; uint32_t trigger_score; @@ -146,15 +146,25 @@ protected: } TriggerData; /* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */ - struct Snapshot - { + struct Snapshot { struct timeval *timestamp; Image *image; void* padding; }; - class MonitorLink - { + //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat +#if 1 + //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit + typedef struct { + uint32_t size; + char event_file[4096]; + timeval recording; // used as both bool and a pointer to the timestamp when recording should begin + //uint32_t frameNumber; + } VideoStoreData; + +#endif // HAVE_LIBAVFORMAT + + class MonitorLink { protected: unsigned int id; char name[64]; @@ -173,6 +183,7 @@ protected: volatile SharedData *shared_data; volatile TriggerData *trigger_data; + volatile VideoStoreData *video_store_data; int last_state; int last_event; @@ -181,21 +192,17 @@ protected: MonitorLink( int p_id, const char *p_name ); ~MonitorLink(); - inline int Id() const - { + inline int Id() const { return( id ); } - inline const char *Name() const - { + inline const char *Name() const { return( name ); } - inline bool isConnected() const - { + inline bool isConnected() const { return( connected ); } - inline time_t getLastConnectTime() const - { + inline time_t getLastConnectTime() const { return( last_connect_time ); } @@ -209,17 +216,25 @@ protected: protected: // These are read from the DB and thereafter remain unchanged - unsigned int id; - char name[64]; - unsigned int server_id; - Function function; // What the monitor is doing - bool enabled; // Whether the monitor is enabled or asleep - unsigned int width; // Normally the same as the camera, but not if partly rotated - unsigned int height; // Normally the same as the camera, but not if partly rotated - bool v4l_multi_buffer; - unsigned int v4l_captures_per_frame; - Orientation orientation; // Whether the image has to be rotated at all - unsigned int deinterlacing; + unsigned int id; + char name[64]; + unsigned int server_id; // Id of the Server object + Function function; // What the monitor is doing + bool enabled; // Whether the monitor is enabled or asleep + unsigned int width; // Normally the same as the camera, but not if partly rotated + unsigned int height; // Normally the same as the camera, but not if partly rotated + bool v4l_multi_buffer; + unsigned int v4l_captures_per_frame; + Orientation orientation; // Whether the image has to be rotated at all + unsigned int deinterlacing; + bool videoRecording; + + int savejpegspref; + VideoWriter videowriter; + std::string encoderparams; + std::vector encoderparamsvec; + bool record_audio; // Whether to store the audio that we receive + int brightness; // The statically saved brightness of the camera int contrast; // The statically saved contrast of the camera int hue; // The statically saved hue of the camera @@ -249,7 +264,7 @@ protected: int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. bool track_motion; // Whether this monitor tries to track detected motion Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected - bool embed_exif; // Whether to embed Exif data into each image frame or not + bool embed_exif; // Whether to embed Exif data into each image frame or not double fps; Image delta_image; @@ -269,7 +284,7 @@ protected: time_t start_time; time_t last_fps_time; time_t auto_resume_time; - unsigned int last_motion_score; + unsigned int last_motion_score; EventCloseMode event_close_mode; @@ -284,6 +299,7 @@ protected: SharedData *shared_data; TriggerData *trigger_data; + VideoStoreData *video_store_data; Snapshot *image_buffer; Snapshot next_buffer; /* Used by four field deinterlacing */ @@ -305,65 +321,102 @@ protected: MonitorLink **linked_monitors; public: + Monitor( int p_id ); // OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info. //bool OurCheckAlarms( Zone *zone, const Image *pImage ); - Monitor( int p_id, const char *p_name, unsigned int p_server_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int label_size, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, double p_analysis_fps, unsigned int p_analysis_update_delay, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, bool p_embed_exif, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 ); + Monitor( + int p_id, + const char *p_name, + unsigned int p_server_id, + int p_function, + bool p_enabled, + const char *p_linked_monitors, + Camera *p_camera, + int p_orientation, + unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, + const char *p_event_prefix, + const char *p_label_format, + const Coord &p_label_coord, + int label_size, + int p_image_buffer_count, + int p_warmup_count, + int p_pre_event_count, + int p_post_event_count, + int p_stream_replay_buffer, + int p_alarm_frame_count, + int p_section_length, + int p_frame_skip, + int p_motion_frame_skip, + double p_analysis_fps, + unsigned int p_analysis_update_delay, + int p_capture_delay, + int p_alarm_capture_delay, + int p_fps_report_interval, + int p_ref_blend_perc, + int p_alarm_ref_blend_perc, + bool p_track_motion, + Rgb p_signal_check_colour, + bool p_embed_exif, + Purpose p_purpose, + int p_n_zones=0, + Zone *p_zones[]=0 + ); ~Monitor(); void AddZones( int p_n_zones, Zone *p_zones[] ); void AddPrivacyBitmask( Zone *p_zones[] ); bool connect(); - inline int ShmValid() const - { + inline int ShmValid() const { return( shared_data->valid ); } - inline int Id() const - { + inline int Id() const { return( id ); } - inline const char *Name() const - { + inline const char *Name() const { return( name ); } - inline Function GetFunction() const - { + inline Function GetFunction() const { return( function ); } - inline bool Enabled() - { + inline bool Enabled() { if ( function <= MONITOR ) return( false ); return( enabled ); } - inline const char *EventPrefix() const - { + inline const char *EventPrefix() const { return( event_prefix ); } - inline bool Ready() - { + inline bool Ready() { if ( function <= MONITOR ) return( false ); return( image_count > ready_count ); } - inline bool Active() - { + inline bool Active() { if ( function <= MONITOR ) return( false ); return( enabled && shared_data->active ); } - inline bool Exif() - { + inline bool Exif() { return( embed_exif ); } + Orientation getOrientation() const; - unsigned int Width() const { return( width ); } - unsigned int Height() const { return( height ); } - unsigned int Colours() const { return( camera->Colours() ); } - unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); } + unsigned int Width() const { return width; } + unsigned int Height() const { return height; } + unsigned int Colours() const; + unsigned int SubpixelOrder() const; + int GetOptSaveJPEGs() const { return( savejpegspref ); } + VideoWriter GetOptVideoWriter() const { return( videowriter ); } + const std::vector* GetOptEncoderParams() const { return( &encoderparamsvec ); } + unsigned int GetPreEventCount() const { return pre_event_count; }; State GetState() const; int GetImage( int index=-1, int scale=100 ); struct timeval GetTimestamp( int index=-1 ) const; @@ -392,19 +445,10 @@ public: int actionColour( int p_colour=-1 ); int actionContrast( int p_contrast=-1 ); - inline int PrimeCapture() - { - return( camera->PrimeCapture() ); - } - inline int PreCapture() - { - return( camera->PreCapture() ); - } + int PrimeCapture(); + int PreCapture(); int Capture(); - int PostCapture() - { - return( camera->PostCapture() ); - } + int PostCapture(); unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. @@ -445,8 +489,7 @@ public: #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) -class MonitorStream : public StreamBase -{ +class MonitorStream : public StreamBase { protected: typedef struct SwapImage { bool valid; @@ -477,19 +520,15 @@ protected: void processCommand( const CmdMsg *msg ); public: - MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) - { + MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) { } - void setStreamBuffer( int p_playback_buffer ) - { + void setStreamBuffer( int p_playback_buffer ) { playback_buffer = p_playback_buffer; } - void setStreamTTL( time_t p_ttl ) - { + void setStreamTTL( time_t p_ttl ) { ttl = p_ttl; } - bool setStreamStart( int monitor_id ) - { + bool setStreamStart( int monitor_id ) { return loadMonitor( monitor_id ); } void runStream(); diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 7b46cd397..e949052dc 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -25,8 +25,7 @@ #include "zm_mpeg.h" #if HAVE_LIBAVCODEC -extern "C" -{ +extern "C" { #include #include } @@ -34,320 +33,284 @@ extern "C" bool VideoStream::initialised = false; VideoStream::MimeData VideoStream::mime_data[] = { - { "asf", "video/x-ms-asf" }, - { "swf", "application/x-shockwave-flash" }, - { "flv", "video/x-flv" }, - { "mov", "video/quicktime" } + { "asf", "video/x-ms-asf" }, + { "swf", "application/x-shockwave-flash" }, + { "flv", "video/x-flv" }, + { "mov", "video/quicktime" } }; -void VideoStream::Initialise( ) -{ - if ( logDebugging() ) +void VideoStream::Initialise( ) { + if ( logDebugging() ) { av_log_set_level( AV_LOG_DEBUG ); - else - av_log_set_level( AV_LOG_QUIET ); - - av_register_all( ); -#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) - avformat_network_init(); -#endif - initialised = true; -} - -void VideoStream::SetupFormat( ) -{ - /* allocate the output media context */ - ofc = NULL; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - avformat_alloc_output_context2( &ofc, NULL, format, filename ); -#else - AVFormatContext *s= avformat_alloc_context(); - if(!s) - { - Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); - } - - AVOutputFormat *oformat; - if (format) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, NULL, NULL); -#else - oformat = guess_format(format, NULL, NULL); -#endif - if (!oformat) { - Fatal( "Requested output format '%s' is not a suitable output format", format ); - } } else { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(NULL, filename, NULL); -#else - oformat = guess_format(NULL, filename, NULL); -#endif - if (!oformat) { - Fatal( "Unable to find a suitable output format for '%s'", format ); - } - } - s->oformat = oformat; - - if (s->oformat->priv_data_size > 0) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if (!s->priv_data) - { - Fatal( "Could not allocate private data for output format." ); - } -#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) - if (s->oformat->priv_class) { - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } -#endif - } - else - { - s->priv_data = NULL; - } - - if(filename) - { - snprintf( s->filename, sizeof(s->filename), "%s", filename ); - } - - ofc = s; -#endif - if ( !ofc ) - { - Fatal( "avformat_alloc_..._context failed: %d", ofc ); + av_log_set_level( AV_LOG_QUIET ); } - of = ofc->oformat; - Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); + av_register_all( ); +#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) + avformat_network_init(); +#endif + initialised = true; } -void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) -{ - /* ffmpeg format matching */ - switch(colours) { - case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } - case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } - case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; - default: - Panic("Unexpected colours: %d",colours); - break; - } - - if ( strcmp( "rtp", of->name ) == 0 ) - { - // RTP must have a packet_size. - // Not sure what this value should be really... - ofc->packet_size = width*height; - - if ( of->video_codec == AV_CODEC_ID_NONE) - { - // RTP does not have a default codec in ffmpeg <= 0.8. - of->video_codec = AV_CODEC_ID_MPEG4; - } - } - - _AVCODECID codec_id = of->video_codec; - if ( codec_name ) - { - AVCodec *a = avcodec_find_encoder_by_name(codec_name); - if ( a ) - { - codec_id = a->id; - } - else - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +void VideoStream::SetupFormat( ) { + /* allocate the output media context */ + ofc = NULL; +#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + avformat_alloc_output_context2( &ofc, NULL, format, filename ); #else - Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); + AVFormatContext *s= avformat_alloc_context(); + if(!s) { + Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); + } + + AVOutputFormat *oformat; + if (format) { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(format, NULL, NULL); +#else + oformat = guess_format(format, NULL, NULL); #endif - } - } - - /* add the video streams using the default format codecs - and initialize the codecs */ - ost = NULL; - if ( codec_id != AV_CODEC_ID_NONE ) - { - codec = avcodec_find_encoder( codec_id ); - if ( !codec ) - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); + if (!oformat) { + Fatal( "Requested output format '%s' is not a suitable output format", format ); + } + } else { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(NULL, filename, NULL); #else - Fatal( "Could not find encoder for '%d'", codec_id ); + oformat = guess_format(NULL, filename, NULL); +#endif + if (!oformat) { + Fatal( "Unable to find a suitable output format for '%s'", format ); + } + } + s->oformat = oformat; + + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (!s->priv_data) { + Fatal( "Could not allocate private data for output format." ); + } +#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data = s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } +#endif + } else { + s->priv_data = NULL; + } + + if ( filename ) { + snprintf( s->filename, sizeof(s->filename), "%s", filename ); + } + + ofc = s; +#endif + if ( !ofc ) { + Fatal( "avformat_alloc_..._context failed: %d", ofc ); + } + + of = ofc->oformat; + Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); +} + +void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) { + /* ffmpeg format matching */ + switch(colours) { + case ZM_COLOUR_RGB24: + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } + case ZM_COLOUR_RGB32: + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } + case ZM_COLOUR_GRAY8: + pf = AV_PIX_FMT_GRAY8; + break; + default: + Panic("Unexpected colours: %d",colours); + break; + } + + if ( strcmp( "rtp", of->name ) == 0 ) { + // RTP must have a packet_size. + // Not sure what this value should be really... + ofc->packet_size = width*height; + + if ( of->video_codec == AV_CODEC_ID_NONE ) { + // RTP does not have a default codec in ffmpeg <= 0.8. + of->video_codec = AV_CODEC_ID_MPEG4; + } + } + + _AVCODECID codec_id = of->video_codec; + if ( codec_name ) { + AVCodec *a = avcodec_find_encoder_by_name(codec_name); + if ( a ) { + codec_id = a->id; + } else { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +#else + Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); #endif } + } + + /* add the video streams using the default format codecs + and initialize the codecs */ + ost = NULL; + if ( codec_id != AV_CODEC_ID_NONE ) { + codec = avcodec_find_encoder( codec_id ); + if ( !codec ) { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); +#else + Fatal( "Could not find encoder for '%d'", codec_id ); +#endif + } #if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); + Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); #else - Debug( 1, "Found encoder for '%d'", codec_id ); + Debug( 1, "Found encoder for '%d'", codec_id ); #endif #if LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) - ost = avformat_new_stream( ofc, codec ); + ost = avformat_new_stream( ofc, codec ); #else - ost = av_new_stream( ofc, 0 ); + ost = av_new_stream( ofc, 0 ); #endif - - if ( !ost ) - { - Fatal( "Could not alloc stream" ); - } - ost->id = ofc->nb_streams - 1; + + if ( !ost ) { + Fatal( "Could not alloc stream" ); + } + ost->id = ofc->nb_streams - 1; - Debug( 1, "Allocated stream" ); + Debug( 1, "Allocated stream" ); - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - c->codec_id = codec->id; - c->codec_type = codec->type; + c->codec_id = codec->id; + c->codec_type = codec->type; - c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; - if ( bitrate <= 100 ) - { - // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. - // This gets rid of artifacts in the beginning of the movie; and well, even quality. - c->flags |= CODEC_FLAG_QSCALE; - c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); - } - else - { - c->bit_rate = bitrate; - } + c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; + if ( bitrate <= 100 ) { + // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. + // This gets rid of artifacts in the beginning of the movie; and well, even quality. + c->flags |= CODEC_FLAG_QSCALE; + c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); + } else { + c->bit_rate = bitrate; + } - /* resolution must be a multiple of two */ - c->width = width; - c->height = height; - /* time base: this is the fundamental unit of time (in seconds) in terms - of which frame timestamps are represented. for fixed-fps content, - timebase should be 1/framerate and timestamp increments should be - identically 1. */ - c->time_base.den = frame_rate; - c->time_base.num = 1; - - Debug( 1, "Will encode in %d fps.", c->time_base.den ); - - /* emit one intra frame every second */ - c->gop_size = frame_rate; + /* resolution must be a multiple of two */ + c->width = width; + c->height = height; + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + c->time_base.den = frame_rate; + c->time_base.num = 1; + + Debug( 1, "Will encode in %d fps.", c->time_base.den ); + + /* emit one intra frame every second */ + c->gop_size = frame_rate; - // some formats want stream headers to be separate - if ( of->flags & AVFMT_GLOBALHEADER ) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - else - { - Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); - } + // some formats want stream headers to be separate + if ( of->flags & AVFMT_GLOBALHEADER ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } else { + Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); + } } -void VideoStream::SetParameters( ) -{ +void VideoStream::SetParameters( ) { } -const char *VideoStream::MimeType( ) const -{ - for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) - { - if ( strcmp( format, mime_data[i].format ) == 0 ) - { - Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); - return ( mime_data[i].mime_type); - } - } - const char *mime_type = of->mime_type; - if ( !mime_type ) - { - std::string mime = "video/"; - mime = mime.append( format ); - mime_type = mime.c_str( ); - Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); - } +const char *VideoStream::MimeType( ) const { + for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) { + if ( strcmp( format, mime_data[i].format ) == 0 ) { + Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); + return ( mime_data[i].mime_type); + } + } + const char *mime_type = of->mime_type; + if ( !mime_type ) { + std::string mime = "video/"; + mime = mime.append( format ); + mime_type = mime.c_str( ); + Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); + } - Debug( 1, "MimeType is \"%s\"", mime_type ); + Debug( 1, "MimeType is \"%s\"", mime_type ); - return ( mime_type); + return ( mime_type); } -void VideoStream::OpenStream( ) -{ - int avRet; +void VideoStream::OpenStream( ) { + int avRet; - /* now that all the parameters are set, we can open the - video codecs and allocate the necessary encode buffers */ - if ( ost ) - { - AVCodecContext *c = ost->codec; - - /* open the codec */ + /* now that all the parameters are set, we can open the + video codecs and allocate the necessary encode buffers */ + if ( ost ) { + AVCodecContext *c = ost->codec; + + /* open the codec */ #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (avRet = avcodec_open( c, codec )) < 0 ) + if ( (avRet = avcodec_open( c, codec )) < 0 ) #else - if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) + if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) #endif - { - Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); - } + { + Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); + } - Debug( 1, "Opened codec" ); + Debug( 1, "Opened codec" ); - /* allocate the encoded raw picture */ + /* allocate the encoded raw picture */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - opicture = av_frame_alloc( ); + opicture = av_frame_alloc( ); #else - opicture = avcodec_alloc_frame( ); + opicture = avcodec_alloc_frame( ); #endif - if ( !opicture ) - { + if ( !opicture ) { Panic( "Could not allocate opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( c->pix_fmt, c->width, - c->height, 1 ); + int size = av_image_get_buffer_size( c->pix_fmt, c->width, c->height, 1 ); #else int size = avpicture_get_size( c->pix_fmt, c->width, c->height ); #endif uint8_t *opicture_buf = (uint8_t *)av_malloc( size ); - if ( !opicture_buf ) - { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); -#else - av_freep( &opicture ); -#endif + if ( !opicture_buf ) { + av_frame_free( &opicture ); Panic( "Could not allocate opicture_buf" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) @@ -362,31 +325,23 @@ void VideoStream::OpenStream( ) picture is needed too. It is then converted to the required output format */ tmp_opicture = NULL; - if ( c->pix_fmt != pf ) - { + if ( c->pix_fmt != pf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - tmp_opicture = av_frame_alloc( ); + tmp_opicture = av_frame_alloc( ); #else - tmp_opicture = avcodec_alloc_frame( ); + tmp_opicture = avcodec_alloc_frame( ); #endif - if ( !tmp_opicture ) - { + if ( !tmp_opicture ) { Panic( "Could not allocate tmp_opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( pf, c->width, - c->height,1 ); + int size = av_image_get_buffer_size( pf, c->width, c->height,1 ); #else int size = avpicture_get_size( pf, c->width, c->height ); #endif uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); - if ( !tmp_opicture_buf ) - { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); -#else - av_freep( &tmp_opicture ); -#endif + if ( !tmp_opicture_buf ) { + av_frame_free( &tmp_opicture ); Panic( "Could not allocate tmp_opicture_buf" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) @@ -401,307 +356,262 @@ void VideoStream::OpenStream( ) } /* open the output file, if needed */ - if ( !(of->flags & AVFMT_NOFILE) ) - { + if ( !(of->flags & AVFMT_NOFILE) ) { int ret; #if LIBAVFORMAT_VERSION_CHECK(53, 15, 0, 21, 0) - ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); + ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); #elif LIBAVFORMAT_VERSION_CHECK(52, 102, 0, 102, 0) - ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); #else - ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); #endif - if ( ret < 0 ) - { - Fatal( "Could not open '%s'", filename ); - } + if ( ret < 0 ) { + Fatal( "Could not open '%s'", filename ); + } - Debug( 1, "Opened output \"%s\"", filename ); - } - else - { - Fatal( "of->flags & AVFMT_NOFILE" ); - } + Debug( 1, "Opened output \"%s\"", filename ); + } else { + Fatal( "of->flags & AVFMT_NOFILE" ); + } - video_outbuf = NULL; - if ( !(of->flags & AVFMT_RAWPICTURE) ) - { - /* allocate output buffer */ - /* XXX: API change will be done */ - // TODO: Make buffer dynamic. - video_outbuf_size = 4000000; - video_outbuf = (uint8_t *)malloc( video_outbuf_size ); - if ( video_outbuf == NULL ) { - Fatal("Unable to malloc memory for outbuf"); - } - } + video_outbuf = NULL; + if ( !(of->flags & AVFMT_RAWPICTURE) ) { + /* allocate output buffer */ + /* XXX: API change will be done */ + // TODO: Make buffer dynamic. + video_outbuf_size = 4000000; + video_outbuf = (uint8_t *)malloc( video_outbuf_size ); + if ( video_outbuf == NULL ) { + Fatal("Unable to malloc memory for outbuf"); + } + } #if LIBAVFORMAT_VERSION_CHECK(52, 101, 0, 101, 0) - av_dump_format(ofc, 0, filename, 1); + av_dump_format(ofc, 0, filename, 1); #else - dump_format(ofc, 0, filename, 1); + dump_format(ofc, 0, filename, 1); #endif #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - int ret = av_write_header( ofc ); + int ret = av_write_header( ofc ); #else - int ret = avformat_write_header( ofc, NULL ); + int ret = avformat_write_header( ofc, NULL ); #endif - if ( ret < 0 ) - { - Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); - } + if ( ret < 0 ) { + Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); + } } VideoStream::VideoStream( const char *in_filename, const char *in_format, int bitrate, double frame_rate, int colours, int subpixelorder, int width, int height ) : - filename(in_filename), - format(in_format), - last_pts( -1 ), - streaming_thread(0), - do_streaming(true), - buffer_copy(NULL), - buffer_copy_lock(new pthread_mutex_t), - buffer_copy_size(0), - buffer_copy_used(0), + filename(in_filename), + format(in_format), + last_pts( -1 ), + streaming_thread(0), + do_streaming(true), + buffer_copy(NULL), + buffer_copy_lock(new pthread_mutex_t), + buffer_copy_size(0), + buffer_copy_used(0), packet_index(0) { - if ( !initialised ) - { - Initialise( ); - } - - if ( format ) - { - int length = strlen(format); - codec_and_format = new char[length+1];; - strcpy( codec_and_format, format ); - format = codec_and_format; - codec_name = NULL; - char *f = strchr(codec_and_format, '/'); - if (f != NULL) - { - *f = 0; - codec_name = f+1; - } - } + if ( !initialised ) { + Initialise( ); + } + + if ( format ) { + int length = strlen(format); + codec_and_format = new char[length+1];; + strcpy( codec_and_format, format ); + format = codec_and_format; + codec_name = NULL; + char *f = strchr(codec_and_format, '/'); + if (f != NULL) { + *f = 0; + codec_name = f+1; + } + } - SetupFormat( ); - SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); - SetParameters( ); - + SetupFormat( ); + SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); + SetParameters( ); + // Allocate buffered packets. packet_buffers = new AVPacket*[2]; packet_buffers[0] = new AVPacket(); packet_buffers[1] = new AVPacket(); packet_index = 0; - - // Initialize mutex used by streaming thread. - if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) - { - Fatal("pthread_mutex_init failed"); - } + + // Initialize mutex used by streaming thread. + if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) { + Fatal("pthread_mutex_init failed"); + } } -VideoStream::~VideoStream( ) -{ - Debug( 1, "VideoStream destructor." ); - - // Stop streaming thread. - if ( streaming_thread ) - { - do_streaming = false; - void* thread_exit_code; +VideoStream::~VideoStream( ) { + Debug( 1, "VideoStream destructor." ); + + // Stop streaming thread. + if ( streaming_thread ) { + do_streaming = false; + void* thread_exit_code; + + Debug( 1, "Asking streaming thread to exit." ); + + // Wait for thread to exit. + pthread_join(streaming_thread, &thread_exit_code); + } + + if ( buffer_copy != NULL ) { + av_free( buffer_copy ); + } - Debug( 1, "Asking streaming thread to exit." ); + if ( buffer_copy_lock ) { + if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) { + Error( "pthread_mutex_destroy failed" ); + } + delete buffer_copy_lock; + } - // Wait for thread to exit. - pthread_join(streaming_thread, &thread_exit_code); - } - - if ( buffer_copy != NULL ) - { - av_free( buffer_copy ); - } - - if ( buffer_copy_lock ) - { - if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) - { - Error( "pthread_mutex_destroy failed" ); - } - delete buffer_copy_lock; - } - if (packet_buffers) { delete packet_buffers[0]; delete packet_buffers[1]; delete[] packet_buffers; } - - /* close each codec */ - if ( ost ) - { - avcodec_close( ost->codec ); - av_free( opicture->data[0] ); -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); -#else - av_freep( &opicture ); -#endif - if ( tmp_opicture ) - { - av_free( tmp_opicture->data[0] ); -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); -#else - av_freep( &tmp_opicture ); -#endif - } - av_free( video_outbuf ); - } + + /* close each codec */ + if ( ost ) { + avcodec_close( ost->codec ); + av_free( opicture->data[0] ); + av_frame_free( &opicture ); + if ( tmp_opicture ) { + av_free( tmp_opicture->data[0] ); + av_frame_free( &tmp_opicture ); + } + av_free( video_outbuf ); + } - /* write the trailer, if any */ - av_write_trailer( ofc ); + /* write the trailer, if any */ + av_write_trailer( ofc ); - /* free the streams */ - for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) - { - av_freep( &ofc->streams[i] ); - } + /* free the streams */ + for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) { + av_freep( &ofc->streams[i] ); + } - if ( !(of->flags & AVFMT_NOFILE) ) - { - /* close the output file */ + if ( !(of->flags & AVFMT_NOFILE) ) { + /* close the output file */ #if LIBAVFORMAT_VERSION_CHECK(52, 105, 0, 105, 0) - avio_close( ofc->pb ); + avio_close( ofc->pb ); #else - url_fclose( ofc->pb ); + url_fclose( ofc->pb ); #endif - } + } - /* free the stream */ - av_free( ofc ); - - /* free format and codec_name data. */ - if ( codec_and_format ) - { - delete codec_and_format; - } + /* free the stream */ + av_free( ofc ); + + /* free format and codec_name data. */ + if ( codec_and_format ) { + delete codec_and_format; + } } -double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) -{ - if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_lock failed." ); - } - - if (buffer_copy_size < buffer_size) - { - if ( buffer_copy ) - { - av_free( buffer_copy ); - } - - // Allocate a buffer to store source images for the streaming thread to encode. - buffer_copy = (uint8_t *)av_malloc( buffer_size ); - if ( !buffer_copy ) - { - Panic( "Could not allocate buffer_copy" ); - } - buffer_copy_size = buffer_size; - } - - add_timestamp = _add_timestamp; - timestamp = _timestamp; - buffer_copy_used = buffer_size; - memcpy(buffer_copy, buffer, buffer_size); - - if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); - } - - if ( streaming_thread == 0 ) - { - Debug( 1, "Starting streaming thread" ); - - // Start a thread for streaming encoded video. - if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ - // Log a fatal error and exit the process. - Fatal( "VideoStream failed to create streaming thread." ); - } - } - - //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); - - return _timestamp; +double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) { + if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_lock failed." ); + } + + if (buffer_copy_size < buffer_size) { + if ( buffer_copy ) { + av_free( buffer_copy ); + } + + // Allocate a buffer to store source images for the streaming thread to encode. + buffer_copy = (uint8_t *)av_malloc( buffer_size ); + if ( !buffer_copy ) { + Panic( "Could not allocate buffer_copy" ); + } + buffer_copy_size = buffer_size; + } + + add_timestamp = _add_timestamp; + timestamp = _timestamp; + buffer_copy_used = buffer_size; + memcpy(buffer_copy, buffer, buffer_size); + + if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); + } + + if ( streaming_thread == 0 ) { + Debug( 1, "Starting streaming thread" ); + + // Start a thread for streaming encoded video. + if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ + // Log a fatal error and exit the process. + Fatal( "VideoStream failed to create streaming thread." ); + } + } + + //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); + + return _timestamp; } -double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) -{ +double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { #ifdef HAVE_LIBSWSCALE - static struct SwsContext *img_convert_ctx = 0; + static struct SwsContext *img_convert_ctx = 0; #endif // HAVE_LIBSWSCALE - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - if ( c->pix_fmt != pf ) - { - memcpy( tmp_opicture->data[0], buffer, buffer_size ); + if ( c->pix_fmt != pf ) { + memcpy( tmp_opicture->data[0], buffer, buffer_size ); #ifdef HAVE_LIBSWSCALE - if ( !img_convert_ctx ) - { - img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); - if ( !img_convert_ctx ) - Panic( "Unable to initialise image scaling context" ); - } - sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); + if ( !img_convert_ctx ) { + img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); + if ( !img_convert_ctx ) + Panic( "Unable to initialise image scaling context" ); + } + sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); #else // HAVE_LIBSWSCALE - Fatal( "swscale is required for MPEG mode" ); + Fatal( "swscale is required for MPEG mode" ); #endif // HAVE_LIBSWSCALE - } - else - { - memcpy( opicture->data[0], buffer, buffer_size ); - } - AVFrame *opicture_ptr = opicture; - - AVPacket *pkt = packet_buffers[packet_index]; - av_init_packet( pkt ); + } else { + memcpy( opicture->data[0], buffer, buffer_size ); + } + AVFrame *opicture_ptr = opicture; + + AVPacket *pkt = packet_buffers[packet_index]; + av_init_packet( pkt ); int got_packet = 0; - if ( of->flags & AVFMT_RAWPICTURE ) - { + if ( of->flags & AVFMT_RAWPICTURE ) { #if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) - pkt->flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; #else - pkt->flags |= PKT_FLAG_KEY; + pkt->flags |= PKT_FLAG_KEY; #endif - pkt->stream_index = ost->index; - pkt->data = (uint8_t *)opicture_ptr; - pkt->size = sizeof (AVPicture); + pkt->stream_index = ost->index; + pkt->data = (uint8_t *)opicture_ptr; + pkt->size = sizeof (AVPicture); got_packet = 1; - } - else - { - opicture_ptr->pts = c->frame_number; - opicture_ptr->quality = c->global_quality; + } else { + opicture_ptr->pts = c->frame_number; + opicture_ptr->quality = c->global_quality; #if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); - if ( ret != 0 ) - { - Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); - } + int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); + if ( ret != 0 ) { + Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); + } #else - int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); - got_packet = out_size > 0 ? 1 : 0; - pkt->data = got_packet ? video_outbuf : NULL; - pkt->size = got_packet ? out_size : 0; + int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); + got_packet = out_size > 0 ? 1 : 0; + pkt->data = got_packet ? video_outbuf : NULL; + pkt->size = got_packet ? out_size : 0; #endif - if ( got_packet ) - { + if ( got_packet ) { // if ( c->coded_frame->key_frame ) // { //#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) @@ -711,12 +621,10 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, //#endif // } - if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { pkt->pts = av_rescale_q( pkt->pts, c->time_base, ost->time_base ); } - if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) { pkt->dts = av_rescale_q( pkt->dts, c->time_base, ost->time_base ); } pkt->duration = av_rescale_q( pkt->duration, c->time_base, ost->time_base ); @@ -728,52 +636,49 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, } int VideoStream::SendPacket(AVPacket *packet) { - - int ret = av_write_frame( ofc, packet ); - if ( ret != 0 ) - { - Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); - } + + int ret = av_write_frame( ofc, packet ); + if ( ret != 0 ) { + Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); + } #if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) av_packet_unref( packet ); #else av_free_packet( packet ); #endif - return ret; + return ret; } void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - + + Debug( 1, "StreamingThreadCallback started" ); + if (ctx == NULL) return NULL; VideoStream* videoStream = reinterpret_cast(ctx); - - const uint64_t nanosecond_multiplier = 1000000000; - - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); - uint64_t frame_count = 0; - timespec start_time; - 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) - { - timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; - uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); - - if ( current_time_ns < target_ns ) - { - // It's not time to render a frame yet. - usleep( (target_ns - current_time_ns) * 0.001 ); - } - + + const uint64_t nanosecond_multiplier = 1000000000; + + uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); + uint64_t frame_count = 0; + timespec start_time; + 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) { + timespec current_time; + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; + uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); + + 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. // If we're encoding the frame before sending it there will be lag. // Since this lag is not constant the client may skip frames. - + // Get the last rendered packet. AVPacket *packet = videoStream->packet_buffers[videoStream->packet_index]; if (packet->size) { @@ -785,29 +690,26 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ av_free_packet( packet ); #endif videoStream->packet_index = videoStream->packet_index ? 0 : 1; - + // Lock buffer and render next frame. - - if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); - } - - if ( videoStream->buffer_copy ) - { - // Encode next frame. - videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); - } - - if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); - } - - frame_count++; - } - - return 0; + + if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); + } + + if ( videoStream->buffer_copy ) { + // Encode next frame. + videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); + } + + if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); + } + + frame_count++; + } + + return 0; } #endif // HAVE_LIBAVCODEC diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp new file mode 100644 index 000000000..8fbb65cb8 --- /dev/null +++ b/src/zm_packet.cpp @@ -0,0 +1,44 @@ +//ZoneMinder Packet Implementation Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packet.h" +#include "zm_ffmpeg.h" + +using namespace std; + +ZMPacket::ZMPacket( AVPacket *p ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + gettimeofday( ×tamp, NULL ); +} + +ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + timestamp = *t; +} + +ZMPacket::~ZMPacket() { + zm_av_packet_unref( &packet ); +} + diff --git a/src/zm_packet.h b/src/zm_packet.h new file mode 100644 index 000000000..9fd7ed8ee --- /dev/null +++ b/src/zm_packet.h @@ -0,0 +1,39 @@ +//ZoneMinder Packet Wrapper Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKET_H +#define ZM_PACKET_H + +extern "C" { +#include +} + +class ZMPacket { + public: + + AVPacket packet; + struct timeval timestamp; + public: + AVPacket *av_packet() { return &packet; } + ZMPacket( AVPacket *packet, struct timeval *timestamp ); + ZMPacket( AVPacket *packet ); + ~ZMPacket(); +}; + +#endif /* ZM_PACKET_H */ diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp new file mode 100644 index 000000000..d4520caba --- /dev/null +++ b/src/zm_packetqueue.cpp @@ -0,0 +1,171 @@ +//ZoneMinder Packet Queue Implementation Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packetqueue.h" +#include "zm_ffmpeg.h" + +#define VIDEO_QUEUESIZE 200 +#define AUDIO_QUEUESIZE 50 + +using namespace std; + +zm_packetqueue::zm_packetqueue(){ + +} + +zm_packetqueue::~zm_packetqueue() { + +} + +bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) { + pktQueue.push_back( zm_packet ); + + return true; +} +bool zm_packetqueue::queuePacket( AVPacket* av_packet ) { + + ZMPacket *zm_packet = new ZMPacket( av_packet ); + + pktQueue.push_back( zm_packet ); + + return true; +} + +ZMPacket* zm_packetqueue::popPacket( ) { + if ( pktQueue.empty() ) { + return NULL; + } + + ZMPacket *packet = pktQueue.front(); + pktQueue.pop_front(); + + return packet; +} + +unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) { + + Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size() ); + frames_to_keep += 1; + + if ( pktQueue.empty() ) { + Debug(3, "Queue is empty"); + return 0; + } + + list::reverse_iterator it; + ZMPacket *packet = NULL; + + for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + + Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + + // Want frames_to_keep video keyframes. Otherwise, we may not have enough + if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { + frames_to_keep --; + } + } + if ( frames_to_keep ) { + Debug(3, "Hit end of queue, still need (%d) video keyframes", frames_to_keep ); + } + unsigned int delete_count = 0; + while ( it != pktQueue.rend() ) { + Debug(4, "Deleting a packet from the front, count is (%d)", delete_count ); + + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + + delete_count += 1; + } + Debug(3, "Deleted (%d) packets", delete_count ); + return delete_count; +} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) + +void zm_packetqueue::clearQueue() { + ZMPacket *packet = NULL; + while(!pktQueue.empty()) { + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + } +} + +unsigned int zm_packetqueue::size() { + return pktQueue.size(); +} + + +void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) { + // Need to find the keyframe <= recording_started. Can get rid of audio packets. + if ( pktQueue.empty() ) { + return; + } + + // Step 1 - find keyframe < recording_started. + // Step 2 - pop packets until we get to the packet in step 2 + list::reverse_iterator it; + + Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId ); + for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + if ( + ( av_packet->flags & AV_PKT_FLAG_KEY ) + && + ( av_packet->stream_index == mVideoStreamId ) + && + timercmp( &(zm_packet->timestamp), recording_started, < ) + ) { + Debug(3, "Found keyframe before start with stream index (%d) with keyframe (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ) ); + break; + } + } + if ( it == pktQueue.rend() ) { + Debug(1, "Didn't find a keyframe packet keeping all" ); + return; + } + + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", + av_packet->stream_index, + ( av_packet->flags & AV_PKT_FLAG_KEY ), + distance( it, pktQueue.rend() ), + pktQueue.size() ); + + unsigned int deleted_frames = 0; + ZMPacket *packet = NULL; + while ( distance( it, pktQueue.rend() ) > 1 ) { + //while ( pktQueue.rend() != it ) { + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + deleted_frames += 1; + } + + zm_packet = pktQueue.front(); + av_packet = &(zm_packet->packet); + if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { + Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + } else { + Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + } +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h new file mode 100644 index 000000000..39160ddfd --- /dev/null +++ b/src/zm_packetqueue.h @@ -0,0 +1,52 @@ +//ZoneMinder Packet Queue Interface Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKETQUEUE_H +#define ZM_PACKETQUEUE_H + +//#include +//#include +//#include +#include +#include "zm_packet.h" + +extern "C" { +#include +} + +class zm_packetqueue { +public: + zm_packetqueue(); + virtual ~zm_packetqueue(); + bool queuePacket( AVPacket* packet, struct timeval *timestamp ); + bool queuePacket( ZMPacket* packet ); + bool queuePacket( AVPacket* packet ); + ZMPacket * popPacket( ); + bool popVideoPacket(ZMPacket* packet); + bool popAudioPacket(ZMPacket* packet); + unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id ); + void clearQueue( ); + unsigned int size(); + void clear_unwanted_packets( timeval *recording, int mVideoStreamId ); +private: + std::list pktQueue; + +}; + +#endif /* ZM_PACKETQUEUE_H */ diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index cc2e49739..e2a77a7f9 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -21,45 +21,57 @@ #include "zm_utils.h" -RemoteCamera::RemoteCamera( int p_id, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), - protocol( p_protocol ), - host( p_host ), - port( p_port ), - path( p_path ), - hp( 0 ) +RemoteCamera::RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_protocol, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ) : + Camera( p_monitor_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), + protocol( p_protocol ), + host( p_host ), + port( p_port ), + path( p_path ), + hp( 0 ) { - if ( path[0] != '/' ) - path = '/'+path; + if ( path[0] != '/' ) + path = '/'+path; } -RemoteCamera::~RemoteCamera() -{ - if(hp != NULL) { +RemoteCamera::~RemoteCamera() { + if ( hp != NULL ) { freeaddrinfo(hp); hp = NULL; } } -void RemoteCamera::Initialise() -{ +void RemoteCamera::Initialise() { if( protocol.empty() ) Fatal( "No protocol specified for remote camera" ); - if( host.empty() ) - Fatal( "No host specified for remote camera" ); + if( host.empty() ) + Fatal( "No host specified for remote camera" ); - if( port.empty() ) - Fatal( "No port specified for remote camera" ); + if( port.empty() ) + Fatal( "No port specified for remote camera" ); - //if( path.empty() ) - //Fatal( "No path specified for remote camera" ); + //if( path.empty() ) + //Fatal( "No path specified for remote camera" ); - // Cache as much as we can to speed things up + // Cache as much as we can to speed things up std::string::size_type authIndex = host.rfind( '@' ); - if ( authIndex != std::string::npos ) - { + if ( authIndex != std::string::npos ) { auth = host.substr( 0, authIndex ); host.erase( 0, authIndex+1 ); auth64 = base64Encode( auth ); @@ -67,21 +79,18 @@ void RemoteCamera::Initialise() authIndex = auth.rfind( ':' ); username = auth.substr(0,authIndex); password = auth.substr( authIndex+1, auth.length() ); - } - mNeedAuth = false; - mAuthenticator = new zm::Authenticator(username,password); + mNeedAuth = false; + mAuthenticator = new zm::Authenticator(username,password); - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); - if ( ret != 0 ) - { + if ( ret != 0 ) { Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); } } - diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index 5378d56a1..b081baeb0 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -27,6 +27,7 @@ #include #include #include +#include // // Class representing 'remote' cameras, i.e. those which are @@ -55,7 +56,22 @@ protected: struct addrinfo *hp; public: - RemoteCamera( int p_id, const std::string &p_proto, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_proto, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); virtual ~RemoteCamera(); const std::string &Protocol() const { return( protocol ); } @@ -73,6 +89,7 @@ public: virtual int PreCapture() = 0; virtual int Capture( Image &image ) = 0; virtual int PostCapture() = 0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0; }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 91476cd41..95c8ae507 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -30,9 +30,39 @@ #ifdef SOLARIS #include // FIONREAD and friends #endif +#ifdef __FreeBSD__ +#include +#endif -RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "http", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ) +RemoteCameraHttp::RemoteCameraHttp( + unsigned int p_monitor_id, + const std::string &p_method, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio ) : + RemoteCamera( + p_monitor_id, + "http", + p_host, + p_port, + p_path, + p_width, + p_height, + p_colours, + p_brightness, + p_contrast, + p_hue, + p_colour, + p_capture, + p_record_audio ) { sd = -1; @@ -44,7 +74,7 @@ RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const else if ( p_method == "regexp" ) method = REGEXP; else - Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); if ( capture ) { Initialise(); @@ -108,12 +138,17 @@ int RemoteCameraHttp::Connect() { close(sd); sd = -1; - Warning("Can't connect to remote camera: %s", strerror(errno) ); + char buf[sizeof(struct in6_addr)]; + struct sockaddr_in *addr; + addr = (struct sockaddr_in *)p->ai_addr; + inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN ); + + Warning("Can't connect to remote camera mid: %d at %s: %s", monitor_id, buf, strerror(errno) ); continue; } - /* If we got here, we must have connected successfully */ - break; + /* If we got here, we must have connected successfully */ + break; } if(p == NULL) { @@ -154,8 +189,7 @@ int RemoteCameraHttp::SendRequest() * > 0 is the # of bytes read. */ -int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) -{ +int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { fd_set rfds; FD_ZERO(&rfds); FD_SET(sd, &rfds); @@ -163,37 +197,49 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) struct timeval temp_timeout = timeout; int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout ); - if( n_found == 0 ) - { + if( n_found == 0 ) { Debug( 4, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. //Disconnect(); return( 0 ); - } - else if ( n_found < 0) - { + } else if ( n_found < 0) { Error( "Select error: %s", strerror(errno) ); return( -1 ); } - int total_bytes_to_read = 0; + unsigned int total_bytes_to_read = 0; - if ( bytes_expected ) - { + if ( bytes_expected ) { total_bytes_to_read = bytes_expected; - } - else - { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) - { + } else { + if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { Error( "Can't ioctl(): %s", strerror(errno) ); return( -1 ); } - if ( total_bytes_to_read == 0 ) - { - if( mode == SINGLE_IMAGE ) - return( 0 ); + if ( total_bytes_to_read == 0 ) { + if ( mode == SINGLE_IMAGE ) { + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } + // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. + return( 0 ); + } // If socket is closed locally, then select will fail, but if it is closed remotely // then we have an exception on our socket.. but no data. Debug( 3, "Socket closed remotely" ); @@ -208,34 +254,27 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) } else { Debug(3, "Just getting %d", total_bytes_to_read ); } - } + } // end if bytes_expected or not Debug( 3, "Expecting %d bytes", total_bytes_to_read ); int total_bytes_read = 0; - do - { + do { int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0) - { + if ( bytes_read < 0 ) { Error( "Read error: %s", strerror(errno) ); return( -1 ); - } - else if ( bytes_read == 0) - { + } else if ( bytes_read == 0 ) { Debug( 2, "Socket closed" ); //Disconnect(); // Disconnect is done outside of ReadData now. return( -1 ); - } - else if ( bytes_read < total_bytes_to_read ) - { + } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); return( -1 ); } Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } - while ( total_bytes_to_read ); + } while ( total_bytes_to_read ); Debug( 4, buffer ); @@ -267,279 +306,282 @@ int RemoteCameraHttp::GetResponse() switch( state ) { case HEADER : - { - static RegExpr *header_expr = 0; - static RegExpr *status_expr = 0; - static RegExpr *connection_expr = 0; - static RegExpr *content_length_expr = 0; - static RegExpr *content_type_expr = 0; + { + static RegExpr *header_expr = 0; + static RegExpr *status_expr = 0; + static RegExpr *connection_expr = 0; + static RegExpr *content_length_expr = 0; + static RegExpr *content_type_expr = 0; - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for REGEXP HEADER"); + } if ( buffer_len < 0 ) { Error( "Unable to read header data" ); return( -1 ); } - if ( !header_expr ) - header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); - if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) - { - header = header_expr->MatchString( 1 ); - header_len = header_expr->MatchLength( 1 ); - Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - - if ( !status_expr ) - status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); - if ( status_expr->Match( header, header_len ) < 4 ) + if ( !header_expr ) + header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); + if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - http_version = status_expr->MatchString( 1 ); - status_code = atoi( status_expr->MatchString( 2 ) ); - status_mesg = status_expr->MatchString( 3 ); + header = header_expr->MatchString( 1 ); + header_len = header_expr->MatchLength( 1 ); + Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - if ( status_code == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); + if ( !status_expr ) + status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); + if ( status_expr->Match( header, header_len ) < 4 ) + { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mNeedAuth = true; - std::string Header = header; - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); - } + http_version = status_expr->MatchString( 1 ); + status_code = atoi( status_expr->MatchString( 2 ) ); + status_mesg = status_expr->MatchString( 3 ); - } else if ( status_code < 200 || status_code > 299 ) { - Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + if ( status_code == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = header; - if ( !connection_expr ) - connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( connection_expr->Match( header, header_len ) == 2 ) - { - connection_type = connection_expr->MatchString( 1 ); - Debug( 3, "Got connection '%s'", connection_type ); - } + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; - if ( !content_length_expr ) - content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( content_length_expr->Match( header, header_len ) == 2 ) - { - content_length = atoi( content_length_expr->MatchString( 1 ) ); - Debug( 3, "Got content length '%d'", content_length ); - } + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } - if ( !content_type_expr ) - content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); - if ( content_type_expr->Match( header, header_len ) >= 2 ) - { - content_type = content_type_expr->MatchString( 1 ); - Debug( 3, "Got content type '%s'\n", content_type ); - if ( content_type_expr->MatchCount() > 2 ) - { - content_boundary = content_type_expr->MatchString( 2 ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", header ); + } else if ( status_code < 200 || status_code > 299 ) { + Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + + if ( !connection_expr ) + connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( connection_expr->Match( header, header_len ) == 2 ) + { + connection_type = connection_expr->MatchString( 1 ); + Debug( 3, "Got connection '%s'", connection_type ); + } + + if ( !content_length_expr ) + content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( content_length_expr->Match( header, header_len ) == 2 ) + { + content_length = atoi( content_length_expr->MatchString( 1 ) ); + Debug( 3, "Got content length '%d'", content_length ); + } + + if ( !content_type_expr ) + content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); + if ( content_type_expr->Match( header, header_len ) >= 2 ) + { + content_type = content_type_expr->MatchString( 1 ); + Debug( 3, "Got content type '%s'\n", content_type ); + if ( content_type_expr->MatchCount() > 2 ) + { + content_boundary = content_type_expr->MatchString( 2 ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) + { + // Image stream, so start processing + if ( !content_boundary[0] ) + { + Error( "No content boundary found in header '%s'", header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} + //} + else + { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + buffer.consume( header_len ); + } else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Debug( 3, "Unable to extract header from stream, retrying" ); + //return( -1 ); } - buffer.consume( header_len ); + break; } - else - { - Debug( 3, "Unable to extract header from stream, retrying" ); - //return( -1 ); - } - break; - } case SUBHEADER : - { - static RegExpr *subheader_expr = 0; - static RegExpr *subcontent_length_expr = 0; - static RegExpr *subcontent_type_expr = 0; - - if ( !subheader_expr ) { - char subheader_pattern[256] = ""; - snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); - subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); - } - if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) - { - subheader = subheader_expr->MatchString( 1 ); - subheader_len = subheader_expr->MatchLength( 1 ); - Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); + static RegExpr *subheader_expr = 0; + static RegExpr *subcontent_length_expr = 0; + static RegExpr *subcontent_type_expr = 0; - if ( !subcontent_length_expr ) - subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + if ( !subheader_expr ) { - content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); - Debug( 3, "Got subcontent length '%d'", content_length ); + char subheader_pattern[256] = ""; + snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); + subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); } - - if ( !subcontent_type_expr ) - subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { - content_type = subcontent_type_expr->MatchString( 1 ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } + subheader = subheader_expr->MatchString( 1 ); + subheader_len = subheader_expr->MatchLength( 1 ); + Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - buffer.consume( subheader_len ); - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + if ( !subcontent_length_expr ) + subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + { + content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); + Debug( 3, "Got subcontent length '%d'", content_length ); + } + + if ( !subcontent_type_expr ) + subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + { + content_type = subcontent_type_expr->MatchString( 1 ); + Debug( 3, "Got subcontent type '%s'", content_type ); + } + + buffer.consume( subheader_len ); + state = CONTENT; } + else + { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting to extract subheader"); + } if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); } - } - break; - } - case CONTENT : - { - - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( (char *)content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { -Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + break; } - else + case CONTENT : { - while ( !content_length ) + + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( (char *)content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + format = JPEG; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + format = X_RGB; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + format = X_RGBZ; + } + else + { + Error( "Found unsupported content type '%s'", content_type ); + return( -1 ); + } + + if ( content_length ) + { + while ( (long)buffer.size() < content_length ) + { + Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } + else + { + while ( !content_length ) + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for content"); + } if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } - static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) - { - if ( !content_expr ) + static RegExpr *content_expr = 0; + if ( mode == MULTI_IMAGE ) { - char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); - } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) - { - content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + if ( !content_expr ) + { + char content_pattern[256] = ""; + snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); + content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); + } + if ( content_expr->Match( buffer, buffer.size() ) == 2 ) + { + content_length = content_expr->MatchLength( 1 ); + Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + } } } } + if ( mode == SINGLE_IMAGE ) + { + state = HEADER; + Disconnect(); + } + else + { + state = SUBHEADER; + } + Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } case HEADERCONT : case SUBHEADERCONT : - { - // Ignore - break; - } + { + // Ignore + break; + } } } } @@ -590,7 +632,7 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char *authenticate_header; static char subcontent_length_header[32]; static char subcontent_type_header[64]; - + static char http_version[16]; static char status_code[16]; static char status_mesg[256]; @@ -600,506 +642,422 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char content_boundary[64]; static int content_boundary_len; - while ( true ) - { - switch( state ) - { + while ( true ) { + switch( state ) { case HEADER : - { - n_headers = 0; - http_header = 0; - connection_header = 0; - content_length_header = 0; - content_type_header = 0; - authenticate_header = 0; + { + n_headers = 0; + http_header = 0; + connection_header = 0; + content_length_header = 0; + content_type_header = 0; + authenticate_header = 0; - http_version[0] = '\0'; - status_code [0]= '\0'; - status_mesg [0]= '\0'; - connection_type [0]= '\0'; - content_length = 0; - content_type[0] = '\0'; - content_boundary[0] = '\0'; - content_boundary_len = 0; - } - case HEADERCONT : - { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + http_version[0] = '\0'; + status_code [0]= '\0'; + status_mesg [0]= '\0'; + connection_type [0]= '\0'; + content_length = 0; + content_type[0] = '\0'; + content_boundary[0] = '\0'; + content_boundary_len = 0; } + case HEADERCONT : + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for HEADERCONT"); + } if ( buffer_len < 0 ) { Error( "Unable to read header" ); return( -1 ); } - char *crlf = 0; - char *header_ptr = (char *)buffer; - int header_len = buffer.size(); - bool all_headers = false; + char *crlf = 0; + char *header_ptr = (char *)buffer; + int header_len = buffer.size(); + bool all_headers = false; - while( true ) - { - int crlf_len = memspn( header_ptr, "\r\n", header_len ); - if ( n_headers ) - { - if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) - { - *header_ptr = '\0'; - header_ptr += crlf_len; + while( true ) { + int crlf_len = memspn( header_ptr, "\r\n", header_len ); + if ( n_headers ) { + if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( header_len == crlf_len ) { + break; + } else { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + } + } + + Debug( 6, "%s", header_ptr ); + if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { + //headers[n_headers++] = header_ptr; + n_headers++; + + if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { + http_header = header_ptr+http_match_len; + Debug( 6, "Got http header '%s'", header_ptr ); + } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { + connection_header = header_ptr+connection_match_len; + Debug( 6, "Got connection header '%s'", header_ptr ); + } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { + content_length_header = header_ptr+content_length_match_len; + Debug( 6, "Got content length header '%s'", header_ptr ); + } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { + authenticate_header = header_ptr; + Debug( 6, "Got authenticate header '%s'", header_ptr ); + } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { + content_type_header = header_ptr+content_type_match_len; + Debug( 6, "Got content type header '%s'", header_ptr ); + } else { + Debug( 6, "Got ignored header '%s'", header_ptr ); + } + header_ptr = crlf; header_len -= buffer.consume( header_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - if ( crlf_len ) - { - if ( header_len == crlf_len ) - { - break; - } - else - { - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - } - - Debug( 6, "%s", header_ptr ); - if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) - { - //headers[n_headers++] = header_ptr; - n_headers++; - - if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) - { - http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); - } - else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) - { - connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); - } - else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) - { - content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); - } - - else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) - { - authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); - } - else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) - { - content_type_header = header_ptr+content_type_match_len; - Debug( 6, "Got content type header '%s'", header_ptr ); - } - else - { - Debug( 6, "Got ignored header '%s'", header_ptr ); - } - header_ptr = crlf; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - else - { - // No end of line found - break; - } - } - - if ( all_headers ) - { - char *start_ptr, *end_ptr; - - if ( !http_header ) - { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - - start_ptr = http_header; - end_ptr = start_ptr+strspn( start_ptr, "10." ); - - memset( http_version, 0, sizeof(http_version) ); - strncpy( http_version, start_ptr, end_ptr-start_ptr ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); - - memset( status_code, 0, sizeof(status_code) ); - strncpy( status_code, start_ptr, end_ptr-start_ptr ); - int status = atoi( status_code ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - strcpy( status_mesg, start_ptr ); - - if ( status == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); - return( -1 ); - } - if ( ! authenticate_header ) { - Error( "Failed authentication, but don't have an authentication header: " ); - return( -1 ); - } - mNeedAuth = true; - std::string Header = authenticate_header; - Debug(2, "Checking for digest auth in %s", authenticate_header ); - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); } else { - Debug( 2, "Need some other kind of Authentication" ); + // No end of line found + break; } - } else if ( status < 200 || status > 299 ) - { - Error( "Invalid response status %s: %s", status_code, status_mesg ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + } // end while search for headers - if ( connection_header ) - { - memset( connection_type, 0, sizeof(connection_type) ); - start_ptr = connection_header + strspn( connection_header, " " ); - strcpy( connection_type, start_ptr ); - Debug( 3, "Got connection '%s'", connection_type ); - } - if ( content_length_header ) - { - start_ptr = content_length_header + strspn( content_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got content length '%d'", content_length ); - } - if ( content_type_header ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = content_type_header + strspn( content_type_header, " " ); - if ( (end_ptr = strchr( start_ptr, ';' )) ) - { - strncpy( content_type, start_ptr, end_ptr-start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); + if ( all_headers ) { + char *start_ptr, *end_ptr; - start_ptr = end_ptr + strspn( end_ptr, "; " ); - - if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) - { - start_ptr += boundary_match_len; - start_ptr += strspn( start_ptr, "-" ); - content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - else - { - Error( "No content boundary found in header '%s'", content_type_header ); - } - } - else - { - strcpy( content_type, start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", content_type_header ); + if ( !http_header ) { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + + start_ptr = http_header; + end_ptr = start_ptr+strspn( start_ptr, "10." ); + + // FIXME WHy are we memsetting every time? Can we not do it once? + memset( http_version, 0, sizeof(http_version) ); + strncpy( http_version, start_ptr, end_ptr-start_ptr ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); + + memset( status_code, 0, sizeof(status_code) ); + strncpy( status_code, start_ptr, end_ptr-start_ptr ); + int status = atoi( status_code ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + strcpy( status_mesg, start_ptr ); + + if ( status == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + if ( ! authenticate_header ) { + Error( "Failed authentication, but don't have an authentication header: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = authenticate_header; + Debug(2, "Checking for digest auth in %s", authenticate_header ); + + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; + + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } else { + Debug( 2, "Need some other kind of Authentication" ); + } + } else if ( status < 200 || status > 299 ) { + Error( "Invalid response status %s: %s", status_code, status_mesg ); + return( -1 ); + } + Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + + if ( connection_header ) { + memset( connection_type, 0, sizeof(connection_type) ); + start_ptr = connection_header + strspn( connection_header, " " ); + // FIXME Should we not use strncpy? + strcpy( connection_type, start_ptr ); + Debug( 3, "Got connection '%s'", connection_type ); + } + if ( content_length_header ) { + start_ptr = content_length_header + strspn( content_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got content length '%d'", content_length ); + } + if ( content_type_header ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = content_type_header + strspn( content_type_header, " " ); + if ( (end_ptr = strchr( start_ptr, ';' )) ) { + strncpy( content_type, start_ptr, end_ptr-start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + + start_ptr = end_ptr + strspn( end_ptr, "; " ); + + if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { + start_ptr += boundary_match_len; + start_ptr += strspn( start_ptr, "-" ); + content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } else { + Error( "No content boundary found in header '%s'", content_type_header ); + } + } else { + strcpy( content_type, start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { + // Image stream, so start processing + if ( !content_boundary[0] ) { + Error( "No content boundary found in header '%s'", content_type_header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} - else - { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + //} + else { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + } else { + Debug( 3, "Unable to extract entire header from stream, continuing" ); + state = HEADERCONT; + //return( -1 ); } + break; } - else - { - Debug( 3, "Unable to extract entire header from stream, continuing" ); - state = HEADERCONT; - //return( -1 ); - } - break; - } case SUBHEADER : - { - n_subheaders = 0; - boundary_header = 0; - subcontent_length_header[0] = '\0'; - subcontent_type_header[0] = '\0'; - content_length = 0; - content_type[0] = '\0'; - } + { + n_subheaders = 0; + boundary_header = 0; + subcontent_length_header[0] = '\0'; + subcontent_type_header[0] = '\0'; + content_length = 0; + content_type[0] = '\0'; + } case SUBHEADERCONT : - { - char *crlf = 0; - char *subheader_ptr = (char *)buffer; - int subheader_len = buffer.size(); - bool all_headers = false; - - while( true ) { - int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); - if ( n_subheaders ) - { - if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; + char *crlf = 0; + char *subheader_ptr = (char *)buffer; + int subheader_len = buffer.size(); + bool all_headers = false; + + while( true ) { + int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); + if ( n_subheaders ) { + if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( subheader_len == crlf_len ) { + break; + } else { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + } + } + + Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + + if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { + //subheaders[n_subheaders++] = subheader_ptr; + n_subheaders++; + + if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { + boundary_header = subheader_ptr; + Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { + strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); + *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; + Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { + strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); + *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; + Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + } else { + Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); + } + subheader_ptr = crlf; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - all_headers = true; + } else { + // No line end found break; } } - if ( crlf_len ) - { - if ( subheader_len == crlf_len ) - { - break; - } - else - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - } - Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + if ( all_headers && boundary_header ) { + char *start_ptr/*, *end_ptr*/; - if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) - { - //subheaders[n_subheaders++] = subheader_ptr; - n_subheaders++; + Debug( 3, "Got boundary '%s'", boundary_header ); - if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) - { - boundary_header = subheader_ptr; - Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + if ( subcontent_length_header[0] ) { + start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got subcontent length '%d'", content_length ); } - else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) - { - strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); - *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; - Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + if ( subcontent_type_header[0] ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); + strcpy( content_type, start_ptr ); + Debug( 3, "Got subcontent type '%s'", content_type ); } - else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) - { - strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); - *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; - Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + state = CONTENT; + } else { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(1, "Timeout waiting to extra subheader non regexp"); } - else - { - Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); - } - subheader_ptr = crlf; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - else - { - // No line end found - break; - } - } - - if ( all_headers && boundary_header ) - { - char *start_ptr/*, *end_ptr*/; - - Debug( 3, "Got boundary '%s'", boundary_header ); - - if ( subcontent_length_header[0] ) - { - start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got subcontent length '%d'", content_length ); - } - if ( subcontent_type_header[0] ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } if ( buffer_len < 0 ) { Error( "Unable to read subheader" ); return( -1 ); } - state = SUBHEADERCONT; + state = SUBHEADERCONT; + } + break; } - break; - } - case CONTENT : - { + case CONTENT : { - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + format = JPEG; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + format = X_RGB; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + format = X_RGBZ; + } else { + Error( "Found unsupported content type '%s'", content_type ); return( -1 ); } - } - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { - //int buffer_len = ReadData( buffer, content_length-buffer.size() ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); + // This is an early test for jpeg content, so we can bail early + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); - } - else - { - int content_pos = 0; - while ( !content_length ) - { - buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) - { - Error( "Unable to read content" ); + + if ( content_length ) { + while ( (long)buffer.size() < content_length ) { + //int buffer_len = ReadData( buffer, content_length-buffer.size() ); + Debug(4, "getting more data"); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } + } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } else { + // Read until we find the end of image or the stream closes. + while ( !content_length ) { + Debug(4, "!content_length, ReadData"); + buffer_len = ReadData( buffer ); + if ( buffer_len < 0 ) + { + Error( "Unable to read content" ); + return( -1 ); + } + int buffer_size = buffer.size(); + if ( buffer_len ) { + // Got some data + + if ( mode == MULTI_IMAGE ) { + // Look for the boundary marker, determine content length using it's position + if ( char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size ) ) { + content_length = start_ptr - (char *)buffer; + Debug( 2, "Got end of image by pattern (crlf--), content-length = %d", content_length ); + } else { + Debug( 2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length ); + } + } // end if MULTI_IMAGE + } else { + content_length = buffer_size; + Debug( 2, "Got end of image by closure, content-length = %d", content_length ); + if ( mode == SINGLE_IMAGE ) { + char *end_ptr = (char *)buffer+buffer_size; + + // strip off any last line feeds + while( *end_ptr == '\r' || *end_ptr == '\n' ) { + content_length--; + end_ptr--; + } + + if ( end_ptr != ((char *)buffer+buffer_size) ) { + Debug( 2, "Trimmed end of image, new content-length = %d", content_length ); + } + } // end if SINGLE_IMAGE + } // end if read some data + } // end while ! content_length + } // end if content_length + + if ( mode == SINGLE_IMAGE ) { + state = HEADER; + Disconnect(); + } else { + state = SUBHEADER; + } + + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } - int buffer_size = buffer.size(); - if ( buffer_len ) - { - if ( mode == MULTI_IMAGE ) - { - while ( char *start_ptr = (char *)memstr( (char *)buffer+content_pos, "\r\n--", buffer_size-content_pos ) ) - { - content_length = start_ptr - (char *)buffer; - Debug( 3, "Got end of image by pattern (crlf--), content-length = %d", content_length ); - break; - } - } - } - else - { - content_length = buffer_size; - Debug( 3, "Got end of image by closure, content-length = %d", content_length ); - if ( mode == SINGLE_IMAGE ) - { - char *end_ptr = (char *)buffer+buffer_size; - - while( *end_ptr == '\r' || *end_ptr == '\n' ) - { - content_length--; - end_ptr--; - } - - if ( end_ptr != ((char *)buffer+buffer_size) ) - { - Debug( 3, "Trimmed end of image, new content-length = %d", content_length ); - } - } - } } - } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); - } - } - - Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } - } + Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); + } // end cast CONTENT + } // end switch } } return( 0 ); @@ -1147,43 +1105,43 @@ int RemoteCameraHttp::Capture( Image &image ) switch( format ) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return( -1 ); + if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) + { + Error( "Unable to decode jpeg" ); + Disconnect(); + return( -1 ); + } + break; } - break; - } case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return( -1 ); + if ( content_length != (long)image.Size() ) + { + Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); + if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) + { + Error( "Unable to unzip RGB image" ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; + } + default : + { + Error( "Unexpected image format encountered" ); Disconnect(); return( -1 ); } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); - Disconnect(); - return( -1 ); - } } return( 0 ); } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index e10175de3..fe5823397 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -30,8 +30,7 @@ // Class representing 'http' cameras, i.e. those which are // accessed over a network connection using http // -class RemoteCameraHttp : public RemoteCamera -{ +class RemoteCameraHttp : public RemoteCamera { protected: std::string request; struct timeval timeout; @@ -45,7 +44,7 @@ protected: enum { SIMPLE, REGEXP } method; public: - RemoteCameraHttp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraHttp(); void Initialise(); @@ -53,11 +52,12 @@ public: int Connect(); int Disconnect(); int SendRequest(); - int ReadData( Buffer &buffer, int bytes_expected=0 ); + int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetResponse(); int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 0f1997198..9b0b6b41d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -28,8 +28,8 @@ #include #include -RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ), +RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), rtsp_describe( p_rtsp_describe ), rtspThread( 0 ) @@ -43,22 +43,23 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const else if ( p_method == "rtpRtspHttp" ) method = RtspThread::RTP_RTSP_HTTP; else - Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor_id ); - if ( capture ) - { + if ( capture ) { Initialise(); } mFormatContext = NULL; mVideoStreamId = -1; + mAudioStreamId = -1; mCodecContext = NULL; mCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; -#if HAVE_LIBSWSCALE +#if HAVE_LIBSWSCALE mConvertContext = NULL; #endif /* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ @@ -74,45 +75,36 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const } else { Panic("Unexpected colours: %d",colours); } - -} +} // end RemoteCameraRtsp::RemoteCameraRtsp(...) -RemoteCameraRtsp::~RemoteCameraRtsp() -{ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) +RemoteCameraRtsp::~RemoteCameraRtsp() { av_frame_free( &mFrame ); av_frame_free( &mRawFrame ); -#else - av_freep( &mFrame ); - av_freep( &mRawFrame ); -#endif #if HAVE_LIBSWSCALE - if ( mConvertContext ) - { + if ( mConvertContext ) { sws_freeContext( mConvertContext ); mConvertContext = NULL; } #endif - if ( mCodecContext ) - { + if ( mCodecContext ) { avcodec_close( mCodecContext ); mCodecContext = NULL; // Freed by avformat_free_context in the destructor of RtspThread class } - if ( capture ) - { + if ( capture ) { Terminate(); } } -void RemoteCameraRtsp::Initialise() -{ +void RemoteCameraRtsp::Initialise() { RemoteCamera::Initialise(); int max_size = width*height*colours; + // This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some + // decent data on how large a buffer is really needed. I think in ffmpeg there are now some functions to do that. buffer.size( max_size ); if ( logDebugging() ) @@ -125,24 +117,20 @@ void RemoteCameraRtsp::Initialise() Connect(); } -void RemoteCameraRtsp::Terminate() -{ +void RemoteCameraRtsp::Terminate() { Disconnect(); } -int RemoteCameraRtsp::Connect() -{ - rtspThread = new RtspThread( id, method, protocol, host, port, path, auth, rtsp_describe ); +int RemoteCameraRtsp::Connect() { + rtspThread = new RtspThread( monitor_id, method, protocol, host, port, path, auth, rtsp_describe ); rtspThread->start(); return( 0 ); } -int RemoteCameraRtsp::Disconnect() -{ - if ( rtspThread ) - { +int RemoteCameraRtsp::Disconnect() { + if ( rtspThread ) { rtspThread->stop(); rtspThread->join(); delete rtspThread; @@ -151,11 +139,9 @@ int RemoteCameraRtsp::Disconnect() return( 0 ); } -int RemoteCameraRtsp::PrimeCapture() -{ +int RemoteCameraRtsp::PrimeCapture() { 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 ); } if ( !rtspThread->hasSources() ) @@ -167,19 +153,41 @@ int RemoteCameraRtsp::PrimeCapture() // Find first video stream present mVideoStreamId = -1; + mAudioStreamId = -1; - for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) + // Find the first video stream. + for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) #else if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) #endif { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + continue; + } else { + Debug(2, "Have another video stream." ); + } } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) +#endif + { + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } + } + } // end foreach stream + if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream" ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream" ); // Get a pointer to the codec context for the video stream mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; @@ -220,7 +228,7 @@ int RemoteCameraRtsp::PrimeCapture() int pSize = avpicture_get_size( imagePixFormat, width, height ); #endif - if( (unsigned int)pSize != imagesize) { + if ( (unsigned int)pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); } /* @@ -241,20 +249,17 @@ int RemoteCameraRtsp::PrimeCapture() return( 0 ); } -int RemoteCameraRtsp::PreCapture() -{ +int RemoteCameraRtsp::PreCapture() { if ( !rtspThread->isRunning() ) return( -1 ); - if ( !rtspThread->hasSources() ) - { + if ( !rtspThread->hasSources() ) { Error( "Cannot precapture, no RTP sources" ); return( -1 ); } return( 0 ); } -int RemoteCameraRtsp::Capture( Image &image ) -{ +int RemoteCameraRtsp::Capture( Image &image ) { AVPacket packet; uint8_t* directbuffer; int frameComplete = false; @@ -266,14 +271,12 @@ int RemoteCameraRtsp::Capture( Image &image ) return (-1); } - while ( true ) - { + while ( true ) { buffer.clear(); if ( !rtspThread->isRunning() ) return (-1); - if ( rtspThread->getFrame( buffer ) ) - { + if ( rtspThread->getFrame( buffer ) ) { Debug( 3, "Read frame %d bytes", buffer.size() ); Debug( 4, "Address %p", buffer.head() ); Hexdump( 4, buffer.head(), 16 ); @@ -281,102 +284,265 @@ int RemoteCameraRtsp::Capture( Image &image ) if ( !buffer.size() ) return( -1 ); - if(mCodecContext->codec_id == AV_CODEC_ID_H264) - { + if(mCodecContext->codec_id == AV_CODEC_ID_H264) { + // SPS and PPS frames should be saved and appended to IDR frames + int nalType = (buffer.head()[3] & 0x1f); + + // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures + if(nalType == 7) { + lastSps = buffer; + continue; + } else if(nalType == 8) { + // PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence + lastPps = buffer; + continue; + } else if(nalType == 5) { + // IDR + buffer += lastSps; + buffer += lastPps; + } + } else { + Debug(3, "Not an h264 packet"); + } + + av_init_packet( &packet ); + + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); + + // So I think this is the magic decode step. Result is a raw image? +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); +#else + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); +#endif + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; + } + // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height ); + + #if HAVE_LIBSWSCALE + if(mConvertContext == NULL) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + #else // HAVE_LIBSWSCALE + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); + #endif // HAVE_LIBSWSCALE + + frameCount++; + + } /* frame complete */ + + zm_av_packet_unref( &packet ); + } /* getFrame() */ + + if(frameComplete) + return (0); + + } // end while true + + // can never get here. + return (0); +} + +//Function to handle capture and store + +int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) { + AVPacket packet; + uint8_t* directbuffer; + int frameComplete = false; + + while ( true ) { + +// WHY Are we clearing it? Might be something good in it. + buffer.clear(); + + if ( !rtspThread->isRunning() ) + return (-1); + + //Video recording + if ( recording.tv_sec ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // Not sure why we are only doing this on keyframe, al + if ( videoStore && (strcmp(oldDirectory, event_file)!=0) ) { + //don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + if ( videoStore ) { + delete videoStore; + videoStore = NULL; + } + } // end if changed to new event + + if ( ! videoStore ) { + //Instantiate the video storage module + + videoStore = new VideoStore((const char *)event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor() ); + strcpy(oldDirectory, event_file); + } // end if ! videoStore + + } else { + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + } // end if recording or not + + if ( rtspThread->getFrame( buffer ) ) { + Debug( 3, "Read frame %d bytes", buffer.size() ); + Debug( 4, "Address %p", buffer.head() ); + Hexdump( 4, buffer.head(), 16 ); + + if ( !buffer.size() ) + return( -1 ); + + if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { // SPS and PPS frames should be saved and appended to IDR frames int nalType = (buffer.head()[3] & 0x1f); // SPS - if(nalType == 7) - { + if(nalType == 7) { lastSps = buffer; continue; - } + } else if(nalType == 8) { // PPS - else if(nalType == 8) - { lastPps = buffer; continue; - } + } else if(nalType == 5) { // IDR - else if(nalType == 5) - { buffer += lastSps; buffer += lastPps; } - } + } // end if H264, what about other codecs? av_init_packet( &packet ); - while ( !frameComplete && buffer.size() > 0 ) - { - packet.data = buffer.head(); - packet.size = buffer.size(); -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); -#else - int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); -#endif - if ( len < 0 ) - { - Error( "Error while decoding frame %d", frameCount ); - Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); - buffer.clear(); - continue; - } - Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - - buffer -= len; + // Keep decoding until a complete frame is had. + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); - } - if ( frameComplete ) { - - Debug( 3, "Got frame %d", frameCount ); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); + // Why are we checking for it being the video stream? Because it might be audio or something else. + // Um... we just initialized packet... we can't be testing for what it is yet.... + if ( packet.stream_index == mVideoStreamId ) { + // So this does the decode +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); #else - avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); #endif + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; + + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(mFrame->data, mFrame->linesize, + directbuffer, imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)mFrame, directbuffer, + imagePixFormat, width, height); +#endif + + } // endif frameComplete + + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket(&packet);//, &lastKeyframePkt); + if ( ret < 0 ) {//Less than zero and we skipped a frame +// Should not + zm_av_packet_unref( &packet ); + return 0; + } + } // end if videoStore, so we are recording #if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + // Why are we re-scaling after writing out the packet? + if ( mConvertContext == NULL ) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context"); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); #endif // HAVE_LIBSWSCALE - - frameCount++; - } /* frame complete */ + frameCount++; + + } else if ( packet.stream_index == mAudioStreamId ) { + Debug( 4, "Got audio packet" ); + if ( videoStore && record_audio ) { + Debug( 4, "Storing Audio packet" ); + //Write the packet to our video store + int ret = videoStore->writeAudioFramePacket( &packet ); //FIXME no relevance of last key frame + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + } // end if video or audio packet -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } /* getFrame() */ - + zm_av_packet_unref( &packet ); + } // end while ! framecomplete and buffer.size() if(frameComplete) return (0); - - } - return (0) ; -} + } /* getFrame() */ -int RemoteCameraRtsp::PostCapture() -{ +} // end while true + +// can never get here. + return (0) ; +} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) + +int RemoteCameraRtsp::PostCapture() { return( 0 ); } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index 079f38a56..8ed8b713c 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -26,6 +26,8 @@ #include "zm_utils.h" #include "zm_rtsp.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'rtsp' cameras, i.e. those which are @@ -51,23 +53,28 @@ protected: RtspThread *rtspThread; int frameCount; - + #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mCodecContext; + AVCodec *mCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; #endif // HAVE_LIBAVFORMAT + bool wasRecording; + VideoStore *videoStore; + char oldDirectory[4096]; + int64_t startTime; #if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; #endif public: - RemoteCameraRtsp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraRtsp(); void Initialise(); @@ -79,7 +86,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); }; #endif // ZM_REMOTE_CAMERA_RTSP_H diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 64106a667..0cd838902 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -64,7 +64,7 @@ RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, c mLastSrTimeReal = tvZero(); mLastSrTimeNtp = tvZero(); mLastSrTimeRtp = 0; - + if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4) Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." ); } @@ -120,7 +120,7 @@ bool RtpSource::updateSeq( uint16_t seq ) { if ( uDelta == 1 ) { - Debug( 3, "Packet in sequence, gap %d", uDelta ); + Debug( 4, "Packet in sequence, gap %d", uDelta ); } else { @@ -198,7 +198,7 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) ); Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime ); - + if ( mBaseTimeNtp.tv_sec == 0 ) { mBaseTimeReal = tvNow(); @@ -262,6 +262,11 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) int rtpHeaderSize = 12 + rtpHeader->cc * 4; // No need to check for nal type as non fragmented packets already have 001 start sequence appended bool h264FragmentEnd = (mCodecId == AV_CODEC_ID_H264) && (packet[rtpHeaderSize+1] & 0x40); + // M stands for Marker, it is the 8th bit + // The interpretation of the marker is defined by a profile. It is intended + // to allow significant events such as frame boundaries to be marked in the + // packet stream. A profile may define additional marker bits or specify + // that there is no marker bit by changing the number of bits in the payload type field. bool thisM = rtpHeader->m || h264FragmentEnd; if ( updateSeq( ntohs(rtpHeader->seqN) ) ) @@ -271,46 +276,55 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) if ( mFrameGood ) { int extraHeader = 0; - + if( mCodecId == AV_CODEC_ID_H264 ) { int nalType = (packet[rtpHeaderSize] & 0x1f); - + Debug( 3, "Have H264 frame: nal type is %d", nalType ); + switch (nalType) { - case 24: - { - extraHeader = 2; - break; - } - case 25: case 26: case 27: - { - extraHeader = 3; - break; - } - // FU-A and FU-B - case 28: case 29: - { - // Is this NAL the first NAL in fragmentation sequence - if ( packet[rtpHeaderSize+1] & 0x80 ) + case 24: // STAP-A { - // Now we will form new header of frame - mFrame.append( "\x0\x0\x1\x0", 4 ); - // Reconstruct NAL header from FU headers - *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | - (packet[rtpHeaderSize] & 0xe0); + extraHeader = 2; + break; + } + case 25: // STAP-B + case 26: // MTAP-16 + case 27: // MTAP-24 + { + extraHeader = 3; + break; + } + // FU-A and FU-B + case 28: case 29: + { + // Is this NAL the first NAL in fragmentation sequence + if ( packet[rtpHeaderSize+1] & 0x80 ) + { + // Now we will form new header of frame + mFrame.append( "\x0\x0\x1\x0", 4 ); + // Reconstruct NAL header from FU headers + *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | + (packet[rtpHeaderSize] & 0xe0); + } + + extraHeader = 2; + break; + } + default: + { + Debug(3, "Unhandled nalType %d", nalType ); } - - extraHeader = 2; - break; - } } - + // Append NAL frame start code if ( !mFrame.size() ) mFrame.append( "\x0\x0\x1", 3 ); } mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader ); + } else { + Debug( 3, "NOT H264 frame: type is %d", mCodecId ); } Hexdump( 4, mFrame.head(), 16 ); @@ -319,12 +333,15 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { if ( mFrameGood ) { - Debug( 2, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() ); + Debug( 3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() ); mFrameProcessed.setValueImmediate( false ); mFrameReady.updateValueSignal( true ); if ( !mFrameProcessed.getValueImmediate() ) { + // What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as + // if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false; + for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ ) if( count > 1 ) return( false ); @@ -377,7 +394,7 @@ bool RtpSource::getFrame( Buffer &buffer ) buffer = mFrame; mFrameReady.setValueImmediate( false ); mFrameProcessed.updateValueSignal( true ); - Debug( 3, "Copied %d bytes", buffer.size() ); + Debug( 4, "Copied %d bytes", buffer.size() ); return( true ); } diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index d08ba166d..f84b2aa53 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -739,8 +739,8 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali { if ( buffer[0] == '$' ) { - if ( buffer.size() < 4 ) - break; + if ( buffer.size() < 4 ) + break; unsigned char channel = buffer[1]; unsigned short len = ntohs( *((unsigned short *)(buffer+2)) ); diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 01b9fa4ed..525d7b8ef 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -302,8 +302,7 @@ void StreamBase::openComms() { Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno) ); lock_fd = 0; - } - else if ( flock(lock_fd, LOCK_EX) != 0 ) + } else if ( flock(lock_fd, LOCK_EX) != 0 ) { Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno) ); close(lock_fd); @@ -318,6 +317,8 @@ void StreamBase::openComms() if ( sd < 0 ) { Fatal( "Can't create socket: %s", strerror(errno) ); + } else { + Debug(3, "Have socket %d", sd ); } length = snprintf( loc_sock_path, sizeof(loc_sock_path), "%s/zms-%06ds.sock", config.path_socks, connkey ); @@ -332,6 +333,7 @@ void StreamBase::openComms() strncpy( loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path) ); loc_addr.sun_family = AF_UNIX; + Debug(3, "Binding to %s", loc_sock_path ); if ( bind( sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1 ) < 0 ) { Fatal( "Can't bind: %s", strerror(errno) ); @@ -341,6 +343,7 @@ void StreamBase::openComms() strncpy( rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path) ); rem_addr.sun_family = AF_UNIX; } // end if connKey > 0 + Debug(3, "comms open" ); } void StreamBase::closeComms() diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index e777a9266..f6ce68089 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -24,8 +24,16 @@ #include #include #include +#if defined(__arm__) +#include +#endif + +#ifdef HAVE_CURL_CURL_H +#include +#endif unsigned int sseversion = 0; +unsigned int neonversion = 0; std::string trimSet(std::string str, std::string trimset) { // Trim Both leading and trailing sets @@ -234,30 +242,59 @@ int pairsplit(const char* string, const char delim, std::string& name, std::stri return 0; } -/* Sets sse_version */ -void ssedetect() { +/* Detect special hardware features, such as SIMD instruction sets */ +void hwcaps_detect() { + neonversion = 0; + sseversion = 0; #if (defined(__i386__) || defined(__x86_64__)) /* x86 or x86-64 processor */ - uint32_t r_edx, r_ecx; - + uint32_t r_edx, r_ecx, r_ebx; + +#ifdef __x86_64__ __asm__ __volatile__( -#if defined(__i386__) - "pushl %%ebx;\n\t" -#endif + "push %%rbx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%rbx\n\t" "mov $0x1,%%eax\n\t" "cpuid\n\t" -#if defined(__i386__) - "popl %%ebx;\n\t" -#endif - : "=d" (r_edx), "=c" (r_ecx) + "pop %%rax\n\t" + "pop %%rbx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : : - : "%eax" -#if !defined(__i386__) - , "%ebx" -#endif ); - - if (r_ecx & 0x00000200) { +#else + __asm__ __volatile__( + "push %%ebx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%ebx\n\t" + "mov $0x1,%%eax\n\t" + "cpuid\n\t" + "pop %%eax\n\t" + "pop %%ebx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : + : + ); +#endif + + if (r_ebx & 0x00000020) { + sseversion = 52; /* AVX2 */ + Debug(1,"Detected a x86\\x86-64 processor with AVX2"); + } else if (r_ecx & 0x10000000) { + sseversion = 51; /* AVX */ + Debug(1,"Detected a x86\\x86-64 processor with AVX"); + } else if (r_ecx & 0x00100000) { + sseversion = 42; /* SSE4.2 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.2"); + } else if (r_ecx & 0x00080000) { + sseversion = 41; /* SSE4.1 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.1"); + } else if (r_ecx & 0x00000200) { sseversion = 35; /* SSSE3 */ Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); } else if (r_ecx & 0x00000001) { @@ -272,12 +309,25 @@ void ssedetect() { } else { sseversion = 0; Debug(1,"Detected a x86\\x86-64 processor"); + } +#elif defined(__arm__) + // ARM processor in 32bit mode + // To see if it supports NEON, we need to get that information from the kernel + unsigned long auxval = getauxval(AT_HWCAP); + if (auxval & HWCAP_ARM_NEON) { + Debug(1,"Detected ARM (AArch32) processor with Neon"); + neonversion = 1; + } else { + Debug(1,"Detected ARM (AArch32) processor"); } - +#elif defined(__aarch64__) + // ARM processor in 64bit mode + // Neon is mandatory, no need to check for it + neonversion = 1; + Debug(1,"Detected ARM (AArch64) processor with Neon"); #else - /* Non x86 or x86-64 processor, SSE2 is not available */ - Debug(1,"Detected a non x86\\x86-64 processor"); - sseversion = 0; + // Unknown processor + Debug(1,"Detected unknown processor architecture"); #endif } @@ -345,3 +395,30 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec } } +char *timeval_to_string( struct timeval tv ) { + time_t nowtime; + struct tm *nowtm; + static char tmbuf[64], buf[64]; + + nowtime = tv.tv_sec; + nowtm = localtime(&nowtime); + strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); + snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec); + return buf; +} + +std::string UriDecode( const std::string &encoded ) { +#ifdef HAVE_LIBCURL + CURL *curl = curl_easy_init(); + int outlength; + char *cres = curl_easy_unescape(curl, encoded.c_str(), encoded.length(), &outlength); + std::string res(cres, cres + outlength); + curl_free(cres); + curl_easy_cleanup(curl); + return res; +#else +Warning("ZM Compiled without LIBCURL. UriDecoding not implemented."); + return encoded; +#endif +} + diff --git a/src/zm_utils.h b/src/zm_utils.h index 6dbf76a4d..961389611 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -54,10 +54,14 @@ inline int min( int a, int b ) return( a<=b?a:b ); } -void ssedetect(); void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); +void hwcaps_detect(); extern unsigned int sseversion; +extern unsigned int neonversion; + +char *timeval_to_string( struct timeval tv ); +std::string UriDecode( const std::string &encoded ); #endif // ZM_UTILS_H diff --git a/src/zm_video.cpp b/src/zm_video.cpp new file mode 100644 index 000000000..ec1371ba3 --- /dev/null +++ b/src/zm_video.cpp @@ -0,0 +1,522 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +#include "zm.h" +#include "zm_video.h" +#include "zm_image.h" +#include "zm_utils.h" +#include "zm_rgb.h" +#include + +VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) : +container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) { + Debug(7,"Video object created"); + + /* Parameter checking */ + if(path.empty()) { + Error("Invalid file path"); + } + if(!width || !height) { + Error("Invalid width or height"); + } + +} + +VideoWriter::~VideoWriter() { + Debug(7,"Video object destroyed"); + +} + +int VideoWriter::Reset(const char* new_path) { + /* Common variables reset */ + + /* If there is a new path, use it */ + if(new_path != NULL) { + path = new_path; + } + + /* Reset frame counter */ + frame_count = 0; + + return 0; +} + + +#if ZM_HAVE_VIDEOWRITER_X264MP4 +X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) { + + /* Initialize ffmpeg if it hasn't been initialized yet */ + FFMPEGInit(); + + /* Initialize swscale */ + zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder); + if(zm_pf == 0) { + Error("Unable to match ffmpeg pixelformat"); + } + codec_pf = AV_PIX_FMT_YUV420P; + + swscaleobj.SetDefaults(zm_pf, codec_pf, width, height); + + /* Calculate the image sizes. We will need this for parameter checking */ + zm_imgsize = colours * width * height; +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + codec_imgsize = av_image_get_buffer_size( codec_pf, width, height, 1 ); +#else + codec_imgsize = avpicture_get_size( codec_pf, width, height); +#endif + if(!codec_imgsize) { + Error("Failed calculating codec pixel format image size"); + } + + /* If supplied with user parameters to the encoder, copy them */ + if(p_user_params != NULL) { + user_params = *p_user_params; + } + + /* Setup x264 parameters */ + if(x264config() < 0) { + Error("Failed setting x264 parameters"); + } + + /* Allocate x264 input picture */ + x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height); + +} + +X264MP4Writer::~X264MP4Writer() { + + /* Free x264 input picture */ + x264_picture_clean(&x264picin); + + if(bOpen) + Close(); + +} + +int X264MP4Writer::Open() { + + /* Open the encoder */ + x264enc = x264_encoder_open(&x264params); + if(x264enc == NULL) { + Error("Failed opening x264 encoder"); + return -1; + } + + // Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc)); + + x264_nal_t* nals; + int i_nals; + if(!x264_encoder_headers(x264enc,&nals,&i_nals)) { + Error("Failed getting encoder headers"); + return -2; + } + + /* Search SPS NAL for AVC information */ + for(int i=0;i 0; i-- ) { + x264encodeloop(true); + } + + /* Close the encoder */ + x264_encoder_close(x264enc); + + /* Close MP4 handle */ + MP4Close(mp4h); + + /* Required for proper HTTP streaming */ + MP4Optimize((path + ".incomplete").c_str(), path.c_str()); + + /* Delete the temporary file */ + unlink((path + ".incomplete").c_str()); + + bOpen = false; + + Debug(7, "Video closed. Total frames: %d", frame_count); + + return 0; +} + +int X264MP4Writer::Reset(const char* new_path) { + + /* Close the encoder and file */ + if(bOpen) + Close(); + + /* Reset common variables */ + VideoWriter::Reset(new_path); + + /* Reset local variables */ + bFirstFrame = true; + bGotH264AVCInfo = false; + prevnals.clear(); + prevpayload.clear(); + + /* Reset x264 parameters */ + x264config(); + + /* Open the encoder */ + Open(); + + return 0; +} + +int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) { + + /* Parameter checking */ + if(data == NULL) { + Error("NULL buffer"); + return -1; + } + + if(data_size != zm_imgsize) { + Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size); + return -2; + } + + if(!bOpen) { + Warning("The encoder was not initialized, initializing now"); + Open(); + } + + /* Convert the image into the x264 input picture */ + if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) { + Error("Image conversion failed"); + return -3; + } + + /* Set PTS */ + x264picin.i_pts = frame_time; + + /* Do the encoding */ + x264encodeloop(); + + /* Increment frame counter */ + frame_count++; + + return 0; +} + +int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) { + + if(img->Width() != width) { + Error("Source image width differs. Source: %d Output: %d",img->Width(), width); + return -12; + } + + if(img->Height() != height) { + Error("Source image height differs. Source: %d Output: %d",img->Height(), height); + return -13; + } + + return Encode(img->Buffer(),img->Size(),frame_time); +} + +int X264MP4Writer::x264config() { + /* Sets up the encoder configuration */ + + int x264ret; + + /* Defaults */ + const char* preset = "veryfast"; + const char* tune = "stillimage"; + const char* profile = "main"; + + /* Search the user parameters for preset, tune and profile */ + for(unsigned int i=0; i < user_params.size(); i++) { + if(strcmp(user_params[i].pname, "preset") == 0) { + /* Got preset */ + preset = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "tune") == 0) { + /* Got tune */ + tune = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "profile") == 0) { + /* Got profile */ + profile = user_params[i].pvalue; + } + } + + /* Set the defaults and preset and tune */ + x264ret = x264_param_default_preset(&x264params, preset, tune); + if(x264ret != 0) { + Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret); + } + + /* Set the profile */ + x264ret = x264_param_apply_profile(&x264params, profile); + if(x264ret != 0) { + Error("Failed setting x264 profile %s : %d",profile,x264ret); + } + + /* Input format */ + x264params.i_width = width; + x264params.i_height = height; + x264params.i_csp = X264_CSP_I420; + + /* Quality control */ + x264params.rc.i_rc_method = X264_RC_CRF; + x264params.rc.f_rf_constant = 23.0; + + /* Enable b-frames */ + x264params.i_bframe = 16; + x264params.i_bframe_adaptive = 1; + + /* Timebase */ + x264params.i_timebase_num = 1; + x264params.i_timebase_den = 1000; + + /* Enable variable frame rate */ + x264params.b_vfr_input = 1; + + /* Disable annex-b (start codes) */ + x264params.b_annexb = 0; + + /* TODO: Setup error handler */ + //x264params.i_log_level = X264_LOG_DEBUG; + + /* Process user parameters (excluding preset, tune and profile) */ + for(unsigned int i=0; i < user_params.size(); i++) { + /* Skip preset, tune and profile */ + if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) { + continue; + } + + /* Pass the name and value to x264 */ + x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue); + + /* Error checking */ + if(x264ret != 0) { + if(x264ret == X264_PARAM_BAD_NAME) { + Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue); + } else if(x264ret == X264_PARAM_BAD_VALUE) { + Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue); + } else { + Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret); + } + } + } + + return 0; +} + +void X264MP4Writer::x264encodeloop(bool bFlush) { + + x264_nal_t* nals; + int i_nals; + int frame_size; + + if(bFlush) { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout); + } else { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout); + } + + if (frame_size > 0 || bFlush) { + Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size); + + /* Handle the previous frame */ + if(!bFirstFrame) { + + buffer.clear(); + + /* Process the NALs for the previous frame */ + for(unsigned int i=0; i < prevnals.size(); i++) { + Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload); + + switch(prevnals[i].i_type) { + case NAL_PPS: + /* PPS NAL */ + MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + case NAL_SPS: + /* SPS NAL */ + MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + default: + /* Anything else, hopefully frames, so copy it into the sample */ + buffer.append(prevnals[i].p_payload, prevnals[i].i_payload); + } + } + + /* Calculate frame duration and offset */ + int duration = x264picout.i_dts - prevDTS; + int offset = prevPTS - prevDTS; + + /* Write the sample */ + if(!buffer.empty()) { + if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) { + Error("Failed writing sample"); + } + } + + /* Cleanup */ + prevnals.clear(); + prevpayload.clear(); + + } + + /* Got a frame. Copy this new frame into the previous frame */ + if(frame_size > 0) { + /* Copy the NALs and the payloads */ + for(int i=0;i* vec) { + if(vec == NULL) { + Error("NULL Encoder parameters vector pointer"); + return -1; + } + + if(str == NULL) { + Error("NULL Encoder parameters string"); + return -2; + } + + vec->clear(); + + if(str[0] == 0) { + /* Empty */ + return 0; + } + + std::string line; + std::stringstream ss(str); + size_t valueoffset; + size_t valuelen; + unsigned int lineno = 0; + EncoderParameter_t param; + + while(std::getline(ss, line) ) { + lineno++; + + /* Remove CR if exists */ + if(line.length() >= 1 && line[line.length()-1] == '\r') { + line.erase(line.length()-1); + } + + /* Skip comments and empty lines */ + if(line.empty() || line[0] == '#') { + continue; + } + + valueoffset = line.find('='); + if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) { + Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); + continue; + } + + + if(valueoffset > (sizeof(param.pname)-1) ) { + Warning("Failed parsing encoder parameters line %d: Name too long", lineno); + continue; + } + + valuelen = line.length() - (valueoffset+1); + + if( valuelen > (sizeof(param.pvalue)-1) ) { + Warning("Failed parsing encoder parameters line %d: Value too long", lineno); + continue; + } + + /* Copy and NULL terminate */ + line.copy(param.pname, valueoffset, 0); + line.copy(param.pvalue, valuelen, valueoffset+1); + param.pname[valueoffset] = 0; + param.pvalue[valuelen] = 0; + + /* Push to the vector */ + vec->push_back(param); + + Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue); + } + + Debug(7, "Parsed %d lines", lineno); + + return 0; +} + + diff --git a/src/zm_video.h b/src/zm_video.h new file mode 100644 index 000000000..936adfeb8 --- /dev/null +++ b/src/zm_video.h @@ -0,0 +1,173 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef ZM_VIDEO_H +#define ZM_VIDEO_H + +#include "zm.h" +#include "zm_rgb.h" +#include "zm_utils.h" +#include "zm_ffmpeg.h" +#include "zm_buffer.h" + +/* +#define HAVE_LIBX264 1 +#define HAVE_LIBMP4V2 1 +#define HAVE_X264_H 1 +#define HAVE_MP4_H 1 +*/ + +#if HAVE_MP4V2_MP4V2_H +#include +#endif +#if HAVE_MP4V2_H +#include +#endif +#if HAVE_MP4_H +#include +#endif + +#if HAVE_X264_H +#ifdef __cplusplus +extern "C" { +#endif +#include +#ifdef __cplusplus +} +#endif +#endif + +/* Structure for user parameters to the encoder */ +struct EncoderParameter_t { + char pname[48]; + char pvalue[48]; + +}; +int ParseEncoderParameters(const char* str, std::vector* vec); + +/* VideoWriter is a generic interface that ZM uses to save events as videos */ +/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */ + +class VideoWriter { + +protected: + std::string container; + std::string codec; + std::string path; + unsigned int width; + unsigned int height; + unsigned int colours; + unsigned int subpixelorder; + + unsigned int frame_count; + +public: + VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); + virtual ~VideoWriter(); + virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0; + virtual int Encode(const Image* img, const unsigned int frame_time) = 0; + virtual int Open() = 0; + virtual int Close() = 0; + virtual int Reset(const char* new_path = NULL); + + const char* GetContainer() const { + return container.c_str(); + } + const char* GetCodec() const { + return codec.c_str(); + } + const char* GetPath() const { + return path.c_str(); + } + unsigned int GetWidth() const { + return width; + } + unsigned int GetHeight() const { + return height; + } + unsigned int GetColours() const { + return colours; + } + unsigned int GetSubpixelorder () const { + return subpixelorder; + } + unsigned int GetFrameCount() const { + return frame_count; + } +}; + +#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE +#define ZM_HAVE_VIDEOWRITER_X264MP4 1 +class X264MP4Writer : public VideoWriter { + +protected: + + bool bOpen; + bool bGotH264AVCInfo; + bool bFirstFrame; + + /* SWScale */ + SWScale swscaleobj; + enum _AVPIXELFORMAT zm_pf; + enum _AVPIXELFORMAT codec_pf; + size_t codec_imgsize; + size_t zm_imgsize; + + /* User parameters */ + std::vector user_params; + + /* AVC Information */ + uint8_t x264_profleindication; + uint8_t x264_profilecompat; + uint8_t x264_levelindication; + + /* NALs */ + Buffer buffer; + + /* Previous frame */ + int prevPTS; + int prevDTS; + bool prevKeyframe; + Buffer prevpayload; + std::vector prevnals; + + /* Internal functions */ + int x264config(); + void x264encodeloop(bool bFlush = false); + + /* x264 objects */ + x264_t* x264enc; + x264_param_t x264params; + x264_picture_t x264picin; + x264_picture_t x264picout; + + /* MP4v2 objects */ + MP4FileHandle mp4h; + MP4TrackId mp4vtid; + + +public: + X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params = NULL); + ~X264MP4Writer(); + int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time); + int Encode(const Image* img, const unsigned int frame_time); + int Open(); + int Close(); + int Reset(const char* new_path = NULL); + +}; +#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE + +#endif // ZM_VIDEO_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp new file mode 100644 index 000000000..e7ad821d9 --- /dev/null +++ b/src/zm_videostore.cpp @@ -0,0 +1,916 @@ +// ZoneMinder Video Storage Implementation +// Written by Chris Wiggins +// http://chriswiggins.co.nz +// Modification by Steve Gilvarry +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#define __STDC_FORMAT_MACROS 1 + +#include +#include +#include + +#include "zm.h" +#include "zm_videostore.h" + +extern "C" { + #include "libavutil/time.h" +} + +VideoStore::VideoStore(const char *filename_in, const char *format_in, + AVStream *p_video_input_stream, + AVStream *p_audio_input_stream, + int64_t nStartTime, + Monitor * monitor + ) { + + video_input_stream = p_video_input_stream; + audio_input_stream = p_audio_input_stream; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + video_input_context = avcodec_alloc_context3( NULL ); + avcodec_parameters_to_context( video_input_context, video_input_stream->codecpar ); +#else + video_input_context = video_input_stream->codec; +#endif + + //store inputs in variables local to class + filename = filename_in; + format = format_in; + + Info("Opening video storage stream %s format: %s\n", filename, format); + + ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename); + if ( ret < 0 ) { + Warning("Could not create video storage stream %s as no output context" + " could be assigned based on filename: %s", + filename, + av_make_error_string(ret).c_str() + ); + } else { + Debug(2, "Success allocating output context"); + } + + //Couldn't deduce format from filename, trying from format name + if ( ! oc ) { + avformat_alloc_output_context2(&oc, NULL, format, filename); + if (!oc) { + Fatal("Could not create video storage stream %s as no output context" + " could not be assigned based on filename or format %s", + filename, format); + } + } else { + Debug(2, "Success alocateing output context"); + } + + AVDictionary *pmetadata = NULL; + int dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + + oc->metadata = pmetadata; + output_format = oc->oformat; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + // Since we are not re-encoding, all we have to do is copy the parameters + video_output_context = avcodec_alloc_context3( NULL ); + + // Copy params from inputstream to context + ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar ); + if ( ret < 0 ) { + Error( "Could not initialize context parameteres"); + return; + } else { + Debug( 2, "Success getting parameters"); + } + + video_output_stream = avformat_new_stream( oc, NULL ); + if ( ! video_output_stream ) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } + + // Now copy them to the output stream + ret = avcodec_parameters_from_context( video_output_stream->codecpar, video_output_context ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return; + } else { + Debug(2, "Success setting parameters"); + } + + zm_dump_stream_format( oc, 0, 0, 1 ); +#else + video_output_stream = avformat_new_stream(oc, (AVCodec*)video_input_context->codec ); + if ( ! video_output_stream ) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } + video_output_context = video_output_stream->codec; + ret = avcodec_copy_context( video_output_context, video_input_context ); + if (ret < 0) { + Fatal("Unable to copy input video context to output video context %s\n", + av_make_error_string(ret).c_str()); + } else { + Debug(3, "Success copying context" ); + } +#endif + + // Just copy them from the input, no reason to choose different + video_output_context->time_base = video_input_context->time_base; + video_output_stream->time_base = video_input_stream->time_base; + + Debug(3, "Time bases: VIDEO input stream (%d/%d) input codec: (%d/%d) output stream: (%d/%d) output codec (%d/%d)", + video_input_stream->time_base.num, + video_input_stream->time_base.den, + video_input_context->time_base.num, + video_input_context->time_base.den, + video_output_stream->time_base.num, + video_output_stream->time_base.den, + video_output_context->time_base.num, + video_output_context->time_base.den + ); + + // WHY? + //video_output_context->codec_tag = 0; + if ( ! video_output_context->codec_tag ) { + Debug(2, "No codec_tag"); + if (! oc->oformat->codec_tag + || av_codec_get_id (oc->oformat->codec_tag, video_input_context->codec_tag) == video_output_context->codec_id + || av_codec_get_tag(oc->oformat->codec_tag, video_input_context->codec_id) <= 0) { + Warning("Setting codec tag"); + video_output_context->codec_tag = video_input_context->codec_tag; + } + } + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + video_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + Monitor::Orientation orientation = monitor->getOrientation(); + Debug(3, "Have orientation" ); + if ( orientation ) { + if ( orientation == Monitor::ROTATE_0 ) { + + } else if ( orientation == Monitor::ROTATE_90 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "90", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_180 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "180", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_270 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "270", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else { + Warning( "Unsupported Orientation(%d)", orientation ); + } + } + + audio_output_codec = NULL; + audio_input_context = NULL; + audio_output_stream = NULL; +#ifdef HAVE_LIBAVRESAMPLE + resample_context = NULL; +#endif + + if ( audio_input_stream ) { + Debug(3, "Have audio stream" ); +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + + audio_input_context = avcodec_alloc_context3( NULL ); + ret = avcodec_parameters_to_context( audio_input_context, audio_input_stream->codecpar ); +#else + audio_input_context = audio_input_stream->codec; +#endif + + if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { + static char error_buffer[256]; + avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 ); + Debug(2, "Got something other than AAC (%s)", error_buffer ); + + + if ( ! setup_resampler() ) { + return; + } + } else { + Debug(3, "Got AAC" ); + + audio_output_stream = avformat_new_stream(oc, (AVCodec*)audio_input_context->codec); + if ( ! audio_output_stream ) { + Error("Unable to create audio out stream\n"); + audio_output_stream = NULL; + } else { + Debug(2, "setting parameters"); + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + audio_output_context = avcodec_alloc_context3( NULL ); + // Copy params from inputstream to context + ret = avcodec_parameters_to_context( audio_output_context, audio_input_stream->codecpar ); +#else + audio_output_context = audio_output_stream->codec; + ret = avcodec_copy_context(audio_output_context, audio_input_context); +#endif + if (ret < 0) { + Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); + audio_output_stream = NULL; + } else { + audio_output_context->codec_tag = 0; + if ( audio_output_context->channels > 1 ) { + Warning("Audio isn't mono, changing it."); + audio_output_context->channels = 1; + } else { + Debug(3, "Audio is mono"); + } + } + } // end if audio_output_stream + } // end if is AAC + + if ( audio_output_stream ) { + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + } + + } // end if audio_input_stream + + /* open the output file, if needed */ + if (!(output_format->flags & AVFMT_NOFILE)) { + ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL); + if (ret < 0) { + Fatal("Could not open output file '%s': %s\n", filename, + av_make_error_string(ret).c_str()); + } + } + + //os->ctx_inited = 1; + //avio_flush(ctx->pb); + //av_dict_free(&opts); + zm_dump_stream_format( oc, 0, 0, 1 ); + if ( audio_output_stream ) + zm_dump_stream_format( oc, 1, 0, 1 ); + + AVDictionary * opts = NULL; + //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + //av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0); + if ((ret = avformat_write_header(oc, NULL)) < 0) { + //if ((ret = avformat_write_header(oc, &opts)) < 0) { + Warning("Unable to set movflags to frag_custom+dash+delay_moov"); + /* Write the stream header, if any. */ + ret = avformat_write_header(oc, NULL); + } else if (av_dict_count(opts) != 0) { + Warning("some options not set\n"); + } + if (ret < 0) { + Error("Error occurred when writing output file header to %s: %s\n", + filename, + av_make_error_string(ret).c_str()); + } + if ( opts ) + av_dict_free(&opts); + + video_last_pts = 0; + video_last_dts = 0; + audio_last_pts = 0; + audio_last_dts = 0; + video_previous_pts = 0; + video_previous_dts = 0; + audio_previous_pts = 0; + audio_previous_dts = 0; + +} // VideoStore::VideoStore + + +VideoStore::~VideoStore(){ + if ( audio_output_codec ) { + // Do we need to flush the outputs? I have no idea. + AVPacket pkt; + int got_packet = 0; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + int64_t size; + + while(1) { +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_receive_packet( audio_output_context, &pkt ); +#else + ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet ); +#endif + if (ret < 0) { + Error("ERror encoding audio while flushing"); + break; + } +Debug(1, "Have audio encoder, need to flush it's output" ); + size += pkt.size; + if (!got_packet) { + break; + } +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.duration > 0) + pkt.duration = av_rescale_q(pkt.duration, audio_output_context->time_base, audio_output_stream->time_base); +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + pkt.stream_index = audio_output_stream->index; + av_interleaved_write_frame( oc, &pkt ); + zm_av_packet_unref( &pkt ); + } // while 1 + } + + // Flush Queues + av_interleaved_write_frame( oc, NULL ); + + /* Write the trailer before close */ + if ( int rc = av_write_trailer(oc) ) { + Error("Error writing trailer %s", av_err2str( rc ) ); + } else { + Debug(3, "Sucess Writing trailer"); + } + + // I wonder if we should be closing the file first. + // I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc. + // What if we were only doing audio recording? + if ( video_output_stream ) { + avcodec_close(video_output_context); + } + if (audio_output_stream) { + avcodec_close(audio_output_context); +#ifdef HAVE_LIBAVRESAMPLE + if ( resample_context ) { + avresample_close( resample_context ); + avresample_free( &resample_context ); + } +#endif + } + + // WHen will be not using a file ? + if (!(output_format->flags & AVFMT_NOFILE)) { + /* Close the output file. */ + if ( int rc = avio_close(oc->pb) ) { + Error("Error closing avio %s", av_err2str( rc ) ); + } + } else { + Debug(3, "Not closing avio because we are not writing to a file."); + } + + /* free the stream */ + avformat_free_context(oc); +} + +bool VideoStore::setup_resampler() { +#ifdef HAVE_LIBAVRESAMPLE + static char error_buffer[256]; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + // Newer ffmpeg wants to keep everything separate... so have to lookup our own decoder, can't reuse the one from the camera. + AVCodec *audio_input_codec = avcodec_find_decoder(audio_input_stream->codecpar->codec_id); +#else + AVCodec *audio_input_codec = avcodec_find_decoder(audio_input_context->codec_id); +#endif + ret = avcodec_open2( audio_input_context, audio_input_codec, NULL ); + if ( ret < 0 ) { + Error("Can't open input codec!"); + return false; + } + + audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( ! audio_output_codec ) { + Error("Could not find codec for AAC"); + return false; + } + Debug(2, "Have audio output codec"); + + //audio_output_context = audio_output_stream->codec; + audio_output_context = avcodec_alloc_context3( audio_output_codec ); + + if ( ! audio_output_context ) { + Error( "could not allocate codec context for AAC\n"); + audio_output_stream = NULL; + return false; + } + + Debug(2, "Have audio_output_context"); + + /* put sample parameters */ + audio_output_context->bit_rate = audio_input_context->bit_rate; + audio_output_context->sample_rate = audio_input_context->sample_rate; + audio_output_context->channels = audio_input_context->channels; + audio_output_context->channel_layout = audio_input_context->channel_layout; + audio_output_context->sample_fmt = audio_input_context->sample_fmt; + audio_output_context->refcounted_frames = 1; + + if ( audio_output_codec->supported_samplerates ) { + int found = 0; + for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { + if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { + found = 1; + break; + } + } + if ( found ) { + Debug(3, "Sample rate is good"); + } else { + audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; + Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); + } + } + + /* check that the encoder supports s16 pcm input */ + if ( ! check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt ) ) { + Debug( 3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name( audio_output_context->sample_fmt)); + audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; + } + + audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; + + + Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d)", + audio_output_context->bit_rate, + audio_output_context->sample_rate, + audio_output_context->channels, + audio_output_context->sample_fmt, + audio_output_context->channel_layout, + audio_output_context->frame_size + ); + + // Now copy them to the output stream + audio_output_stream = avformat_new_stream( oc, audio_output_codec ); + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_parameters_from_context( audio_output_stream->codecpar, audio_output_context ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return false; + } +#endif + + AVDictionary *opts = NULL; + av_dict_set( &opts, "strict", "experimental", 0); + ret = avcodec_open2( audio_output_context, audio_output_codec, &opts ); + av_dict_free(&opts); + if ( ret < 0 ) { + av_strerror(ret, error_buffer, sizeof(error_buffer)); + Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); + audio_output_codec = NULL; + audio_output_context = NULL; + audio_output_stream = NULL; + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(input_frame = zm_av_frame_alloc())) { + Error("Could not allocate input frame"); + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(output_frame = zm_av_frame_alloc())) { + Error("Could not allocate output frame"); + av_frame_free( &input_frame ); + return false; + } + + // Setup the audio resampler + resample_context = avresample_alloc_context(); + if ( ! resample_context ) { + Error( "Could not allocate resample context\n"); + return false; + } + + // Some formats (i.e. WAV) do not produce the proper channel layout + if ( audio_input_context->channel_layout == 0 ) { + Error( "Bad channel layout. Need to set it to mono.\n"); + av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( "mono" ), 0 ); + } else { + av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 ); + } + + av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0); + av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0); + av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0); + //av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0); + av_opt_set_int( resample_context, "out_channel_layout", av_get_channel_layout( "mono" ), 0 ); + av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0); + av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0); + av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0); + + ret = avresample_open( resample_context ); + if ( ret < 0 ) { + Error( "Could not open resample context\n"); + return false; + } + +#if 0 + /** + * Allocate as many pointers as there are audio channels. + * Each pointer will later point to the audio samples of the corresponding + * channels (although it may be NULL for interleaved formats). + */ + if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) { + Error( "Could not allocate converted input sample pointers\n"); + return; + } + /** + * Allocate memory for the samples of all channels in one consecutive + * block for convenience. + */ + if ((ret = av_samples_alloc( &converted_input_samples, NULL, + audio_output_context->channels, + audio_output_context->frame_size, + audio_output_context->sample_fmt, 0)) < 0) { + Error( "Could not allocate converted input samples (error '%s')\n", + av_make_error_string(ret).c_str() ); + + av_freep(converted_input_samples); + free(converted_input_samples); + return; + } +#endif + + output_frame->nb_samples = audio_output_context->frame_size; + output_frame->format = audio_output_context->sample_fmt; + output_frame->channel_layout = audio_output_context->channel_layout; + + // The codec gives us the frame size, in samples, we calculate the size of the samples buffer in bytes + unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( NULL, audio_output_context->channels, audio_output_context->frame_size, audio_output_context->sample_fmt, 0 ); + converted_input_samples = (uint8_t*) av_malloc( audioSampleBuffer_size ); + + if ( !converted_input_samples ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + + // Setup the data pointers in the AVFrame + if ( avcodec_fill_audio_frame( + output_frame, + audio_output_context->channels, + audio_output_context->sample_fmt, + (const uint8_t*) converted_input_samples, + audioSampleBuffer_size, 0 ) < 0 ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + + return true; +#else + Error("Not built with libavresample library. Cannot do audio conversion to AAC"); + return false; +#endif +} + + +void VideoStore::dumpPacket( AVPacket *pkt ){ + char b[10240]; + + snprintf(b, sizeof(b), " pts: %" PRId64 ", dts: %" PRId64 ", data: %p, size: %d, sindex: %d, dflags: %04x, s-pos: %" PRId64 ", c-duration: %" PRId64 "\n" + , pkt->pts + , pkt->dts + , pkt->data + , pkt->size + , pkt->stream_index + , pkt->flags + , pkt->pos + , pkt->duration + ); + Debug(1, "%s:%d:DEBUG: %s", __FILE__, __LINE__, b); +} + +int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { + av_init_packet(&opkt); + + int duration; + + //Scale the PTS of the outgoing packet to be the correct time base + if (ipkt->pts != AV_NOPTS_VALUE) { + + if ( ! video_last_pts ) { + // This is the first packet. + opkt.pts = 0; + Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts ); + } else { + if ( ipkt->pts < video_last_pts ) { + Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts ); + // wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok? + opkt.pts = video_previous_pts + av_rescale_q( ipkt->pts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.pts = video_previous_pts + av_rescale_q( ipkt->pts - video_last_pts, video_input_stream->time_base, video_output_stream->time_base); + } + } + Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts ); + duration = ipkt->pts - video_last_pts; + video_last_pts = ipkt->pts; + } else { + Debug(3, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + + // Just because the input stream wraps, doesn't mean the output needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the output. So need to handle input wrap, without causing output wrap. + if ( ! video_last_dts ) { + // This is the first packet. + opkt.dts = 0; + Debug(1, "Starting video video_last_dts will become (%d)", ipkt->dts ); + video_last_dts = ipkt->dts; + } else { + if ( ipkt->dts == AV_NOPTS_VALUE ) { + // why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base + if ( video_input_stream->cur_dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_input_stream->cur_dts, ipkt->dts ); + opkt.dts = video_previous_dts + av_rescale_q(video_input_stream->cur_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } else { + opkt.dts = video_previous_dts + av_rescale_q(video_input_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from video_input_stream->cur_dts(%d) - previus_dts(%d)", opkt.dts, video_input_stream->cur_dts, video_last_dts ); + video_last_dts = video_input_stream->cur_dts; + } else { + if ( ipkt->dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts ); + opkt.dts = video_previous_dts + av_rescale_q( ipkt->dts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.dts = video_previous_dts + av_rescale_q( ipkt->dts - video_last_dts, video_input_stream->time_base, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)", opkt.dts, ipkt->dts, video_last_dts ); + video_last_dts = ipkt->dts; + } + } + if ( opkt.dts > opkt.pts ) { + Debug( 1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + if ( ipkt->duration == AV_NOPTS_VALUE ) { + opkt.duration = av_rescale_q( duration, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base); + } + opkt.flags = ipkt->flags; + opkt.pos=-1; + + opkt.data = ipkt->data; + opkt.size = ipkt->size; + + // Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0 + if ( ipkt->stream_index > 0 and ! audio_output_stream ) { + Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index ); + opkt.stream_index = 0; + } else { + opkt.stream_index = ipkt->stream_index; + } + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + +Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration ); + if ((opkt.data == NULL)||(opkt.size < 1)) { + Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); + dumpPacket( ipkt); + dumpPacket(&opkt); + + } else if ((video_previous_dts > 0) && (video_previous_dts > opkt.dts)) { + Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, video_previous_dts, opkt.dts); + video_previous_dts = opkt.dts; + dumpPacket(&opkt); + + } else { + + video_previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + video_previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret<0){ + // There's nothing we can really do if the frame is rejected, just drop it and get on with the next + Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); + dumpPacket(&safepkt); + } + } + + zm_av_packet_unref(&opkt); + + return 0; + +} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) + +int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { + Debug(4, "writeAudioFrame"); + + if ( ! audio_output_stream ) { + Debug(1, "Called writeAudioFramePacket when no audio_output_stream"); + return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment + } + + if ( audio_output_codec ) { +#ifdef HAVE_LIBAVRESAMPLE + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_send_packet( audio_input_context, ipkt ); + if ( ret < 0 ) { + Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } + + ret = avcodec_receive_frame( audio_input_context, input_frame ); + if ( ret < 0 ) { + Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); + return 0; + } + Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)", + input_frame->nb_samples, + input_frame->format, + input_frame->sample_rate, + input_frame->channel_layout, + audio_output_context->refcounted_frames + ); +#else + /** + * Decode the audio frame stored in the packet. + * The input audio stream decoder is used to do this. + * If we are at the end of the file, pass an empty packet to the decoder + * to flush it. + */ + if ((ret = avcodec_decode_audio4(audio_input_context, input_frame, + &data_present, ipkt)) < 0) { + Error( "Could not decode frame (error '%s')\n", + av_make_error_string(ret).c_str()); + dumpPacket( ipkt ); + av_frame_free( &input_frame ); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to transcode a frame yet."); + return 0; + } +#endif + int frame_size = input_frame->nb_samples; + Debug(4, "Frame size: %d", frame_size ); + + // Resample the input into the audioSampleBuffer until we proceed the whole decoded data + if ( (ret = avresample_convert( resample_context, + NULL, + 0, + 0, + input_frame->data, + 0, + input_frame->nb_samples )) < 0 ) { + Error( "Could not resample frame (error '%s')\n", + av_make_error_string(ret).c_str()); + return 0; + } + + if ( avresample_available( resample_context ) < output_frame->nb_samples ) { + Debug(1, "No enough samples yet"); + return 0; + } + + // Read a frame audio data from the resample fifo + if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) { + Warning( "Error reading resampled audio: " ); + return 0; + } + + av_init_packet(&opkt); + Debug(5, "after init packet" ); + + /** Set a timestamp based on the sample rate for the container. */ + //output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); + + // convert the packet to the codec timebase from the stream timebase + //Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, + //av_frame_get_best_effort_timestamp(output_frame) + //); + /** + * Encode the audio frame and store it in the temporary packet. + * The output audio stream encoder is used to do this. + */ +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + if (( ret = avcodec_send_frame( audio_output_context, output_frame ) ) < 0 ) { + Error( "Could not send frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + + if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) { + Error( "Could not recieve packet (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } +#else + if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) { + Error( "Could not encode frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to output a frame yet."); + zm_av_packet_unref(&opkt); + return 0; + } +#endif + +#endif + } else { + av_init_packet(&opkt); + Debug(5, "after init packet" ); + opkt.data = ipkt->data; + opkt.size = ipkt->size; + } + + // PTS is difficult, because of the buffering of the audio packets in the resampler. So we have to do it once we actually have a packet... + + //Scale the PTS of the outgoing packet to be the correct time base + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( ! audio_last_pts ) { + opkt.pts = 0; + Debug(1, "No audio_last_pts"); + } else { + if ( audio_last_pts > ipkt->pts ) { + Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts ); + opkt.pts = audio_previous_pts + av_rescale_q(ipkt->pts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.pts = audio_previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "audio opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts ); + } + audio_last_pts = ipkt->pts; + } else { + Debug(2, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + if ( ! audio_last_dts ) { + opkt.dts = 0; + + } else { + if( ipkt->dts == AV_NOPTS_VALUE ) { + // So if the input has no dts assigned... still need an output dts... so we use cur_dts? + + if ( audio_last_dts > audio_input_stream->cur_dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts ); + opkt.dts = audio_previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } else { + opkt.dts = audio_previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } + audio_last_dts = audio_input_stream->cur_dts; + Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts ); + } else { + if ( audio_last_dts > ipkt->dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts ); + opkt.dts = audio_previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.dts = audio_previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts ); + } + } + audio_last_dts = ipkt->dts; + if ( opkt.dts > opkt.pts ) { + Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + // I wonder if we could just use duration instead of all the hoop jumping above? + opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); + Debug( 2, "opkt.pts (%d), opkt.dts(%d) opkt.duration = (%d)", opkt.pts, opkt.dts, opkt.duration ); + + // pkt.pos: byte position in stream, -1 if unknown + opkt.pos = -1; + opkt.stream_index = ipkt->stream_index; + Debug(2, "Stream index is %d", opkt.stream_index ); + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + audio_previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + audio_previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret!=0){ + Error("Error writing audio frame packet: %s\n", av_make_error_string(ret).c_str()); + dumpPacket(&safepkt); + } else { + Debug(2,"Success writing audio frame" ); + } + zm_av_packet_unref(&opkt); + return 0; +} // end int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) + diff --git a/src/zm_videostore.h b/src/zm_videostore.h new file mode 100644 index 000000000..e4d337df0 --- /dev/null +++ b/src/zm_videostore.h @@ -0,0 +1,84 @@ +#ifndef ZM_VIDEOSTORE_H +#define ZM_VIDEOSTORE_H + +#include "zm_ffmpeg.h" +extern "C" { +#include "libavutil/audio_fifo.h" + +#ifdef HAVE_LIBAVRESAMPLE +#include "libavresample/avresample.h" +#endif +} + +#if HAVE_LIBAVCODEC + +#include "zm_monitor.h" + +class VideoStore { +private: + unsigned int packets_written; + + AVOutputFormat *output_format; + AVFormatContext *oc; + AVStream *video_output_stream; + AVStream *audio_output_stream; + AVCodecContext *video_output_context; + + AVStream *video_input_stream; + AVStream *audio_input_stream; + + // Move this into the object so that we aren't constantly allocating/deallocating it on the stack + AVPacket opkt; + // we are transcoding + AVFrame *input_frame; + AVFrame *output_frame; + + AVCodecContext *video_input_context; + AVCodecContext *audio_input_context; + int ret; + + // The following are used when encoding the audio stream to AAC + AVCodec *audio_output_codec; + AVCodecContext *audio_output_context; + int data_present; + AVAudioFifo *fifo; + int output_frame_size; +#ifdef HAVE_LIBAVRESAMPLE +AVAudioResampleContext* resample_context; +#endif + uint8_t *converted_input_samples = NULL; + + const char *filename; + const char *format; + + bool keyframeMessage; + int keyframeSkipNumber; + + // These are for input + int64_t video_last_pts; + int64_t video_last_dts; + int64_t audio_last_pts; + int64_t audio_last_dts; + + // These are for output, should start at zero. We assume they do not wrap because we just aren't going to save files that big. + int64_t video_previous_pts; + int64_t video_previous_dts; + int64_t audio_previous_pts; + int64_t audio_previous_dts; + + int64_t filter_in_rescale_delta_last; + + bool setup_resampler(); + +public: + VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor ); + ~VideoStore(); + + int writeVideoFramePacket( AVPacket *pkt ); + int writeAudioFramePacket( AVPacket *pkt ); + void dumpPacket( AVPacket *pkt ); +}; + +#endif //havelibav +#endif //zm_videostore_h + diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 90ba855dc..eaaad56c5 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -23,8 +23,28 @@ #include "zm_image.h" #include "zm_monitor.h" -void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs, int p_overload_frames, int p_extend_alarm_frames ) -{ +void Zone::Setup( + Monitor *p_monitor, + int p_id, + const char *p_label, + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold, + int p_max_pixel_threshold, + int p_min_alarm_pixels, + int p_max_alarm_pixels, + const Coord &p_filter_box, + int p_min_filter_pixels, + int p_max_filter_pixels, + int p_min_blob_pixels, + int p_max_blob_pixels, + int p_min_blobs, + int p_max_blobs, + int p_overload_frames, + int p_extend_alarm_frames +) { monitor = p_monitor; id = p_id; @@ -46,7 +66,7 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_ min_blobs = p_min_blobs; max_blobs = p_max_blobs; overload_frames = p_overload_frames; - extend_alarm_frames = p_extend_alarm_frames; + extend_alarm_frames = p_extend_alarm_frames; Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); @@ -64,144 +84,113 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_ overload_count = 0; extend_alarm_count = 0; - pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); + pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE ); pg_image->Clear(); pg_image->Fill( 0xff, polygon ); pg_image->Outline( 0xff, polygon ); ranges = new Range[monitor->Height()]; - for ( unsigned int y = 0; y < monitor->Height(); y++) - { + for ( unsigned int y = 0; y < monitor->Height(); y++ ) { ranges[y].lo_x = -1; ranges[y].hi_x = 0; ranges[y].off_x = 0; const uint8_t *ppoly = pg_image->Buffer( 0, y ); - for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) - { - if ( *ppoly ) - { - if ( ranges[y].lo_x == -1 ) - { + for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) { + if ( *ppoly ) { + if ( ranges[y].lo_x == -1 ) { ranges[y].lo_x = x; } - if ( (unsigned int)ranges[y].hi_x < x ) - { + if ( (unsigned int)ranges[y].hi_x < x ) { ranges[y].hi_x = x; } } } } - - if ( config.record_diag_images ) - { + + // FIXME: Is this not a problem? If you had two zones for a monitor.. then these would conflict. You would only get 1 dump file + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( ! diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-poly.jpg", config.dir_events, monitor->Name(), id); } pg_image->WriteJpeg( diag_path ); } -} +} // end Zone::Setup -Zone::~Zone() -{ +Zone::~Zone() { delete[] label; delete image; delete pg_image; delete[] ranges; } -void Zone::RecordStats( const Event *event ) -{ - static char sql[ZM_SQL_MED_BUFSIZ]; +void Zone::RecordStats( const Event *event ) { + static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Stats set MonitorId=%d, ZoneId=%d, EventId=%d, FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", monitor->Id(), id, event->Id(), event->Frames()+1, pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert event stats: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } -} +} // end void Zone::RecordStats( const Event *event ) - -//============================================================================= -bool Zone::CheckOverloadCount() -{ +bool Zone::CheckOverloadCount() { Info("Overloaded count: %d, Overloaded frames: %d", overload_count, overload_frames); - if ( overload_count ) - { - Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - overload_count--; - return( false ); + if ( overload_count ) { + Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + overload_count--; + return( false ); } return true; -} +} // end bool Zone::CheckOverloadCount() -void Zone::SetScore(unsigned int nScore) -{ +void Zone::SetScore(unsigned int nScore) { score = nScore; -} +} // end void Zone::SetScore(unsigned int nScore) - -void Zone::SetAlarmImage(const Image* srcImage) -{ +void Zone::SetAlarmImage(const Image* srcImage) { delete image; image = new Image(*srcImage); -} +} // end void Zone::SetAlarmImage( const Image* srcImage ) -int Zone::GetOverloadCount() -{ +int Zone::GetOverloadCount() { return overload_count; -} +} // end int Zone::GetOverloadCount() -void Zone::SetOverloadCount(int nOverCount) -{ +void Zone::SetOverloadCount(int nOverCount) { overload_count = nOverCount; -} +} // end void Zone::SetOverloadCount(int nOverCount ) -int Zone::GetOverloadFrames() -{ +int Zone::GetOverloadFrames() { return overload_frames; -} +} // end int Zone::GetOverloadFrames -int Zone::GetExtendAlarmCount() -{ +int Zone::GetExtendAlarmCount() { return extend_alarm_count; -} +} // end int Zone::GetExtendAlarmCount() -void Zone::SetExtendAlarmCount(int nExtendAlarmCount) -{ +void Zone::SetExtendAlarmCount(int nExtendAlarmCount) { extend_alarm_count = nExtendAlarmCount; -} +} // end void Zone::SetExtendAlarmCount( int nExtendAlarmCount ) -int Zone::GetExtendAlarmFrames() -{ +int Zone::GetExtendAlarmFrames() { return extend_alarm_frames; -} +} // end int Zone::GetExtendAlarmFrames() -bool Zone::CheckExtendAlarmCount() -{ - Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames); - if ( extend_alarm_count ) - { - Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); - extend_alarm_count--; - return( true ); +bool Zone::CheckExtendAlarmCount() { + Info( "ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames ); + if ( extend_alarm_count ) { + Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); + extend_alarm_count--; + return( true ); } return false; -} +} // end bool Zone::CheckExtendAlarmCount - -//=========================================================================== - - - -bool Zone::CheckAlarms( const Image *delta_image ) -{ +bool Zone::CheckAlarms( const Image *delta_image ) { ResetStats(); - if ( overload_count ) - { + if ( overload_count ) { Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); overload_count--; @@ -225,33 +214,29 @@ bool Zone::CheckAlarms( const Image *delta_image ) int alarm_mid_x = -1; int alarm_mid_y = -1; - + unsigned int lo_y = polygon.LoY(); unsigned int lo_x = polygon.LoX(); unsigned int hi_x = polygon.HiX(); unsigned int hi_y = polygon.HiY(); Debug( 4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y ); - - - Debug( 5, "Checking for alarmed pixels" ); + /* if(config.cpu_extensions && sseversion >= 20) { - sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } else { - std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } */ + sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } else { + std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } */ std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - - if ( config.record_diag_images ) - { + + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( ! diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-%d.jpg", config.dir_events, monitor->Name(), id, 1 ); } diff_image->WriteJpeg( diag_path ); } - + if ( pixel_diff_count && alarm_pixels ) pixel_diff = pixel_diff_count/alarm_pixels; Debug( 5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff ); @@ -269,63 +254,52 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No alarmed pixels */ return (false); } - + score = (100*alarm_pixels)/polygon.Area(); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); - - if ( check_method >= FILTERED_PIXELS ) - { + + if ( check_method >= FILTERED_PIXELS ) { int bx = filter_box.X(); int by = filter_box.Y(); int bx1 = bx-1; int by1 = by-1; Debug( 5, "Checking for filtered pixels" ); - if ( bx > 1 || by > 1 ) - { + if ( bx > 1 || by > 1 ) { // Now remove any pixels smaller than our filter size unsigned char *cpdiff; int ldx, hdx, ldy, hdy; bool block; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { + if ( *pdiff == WHITE ) { // Check participation in an X block ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); ldy = (y>=(lo_y+by1))?-by1:lo_y-y; hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); block = false; - for ( int dy = ldy; !block && dy <= hdy; dy++ ) - { - for ( int dx = ldx; !block && dx <= hdx; dx++ ) - { + for ( int dy = ldy; !block && dy <= hdy; dy++ ) { + for ( int dx = ldx; !block && dx <= hdx; dx++ ) { block = true; - for ( int dy2 = 0; block && dy2 < by; dy2++ ) - { - for ( int dx2 = 0; block && dx2 < bx; dx2++ ) - { + for ( int dy2 = 0; block && dy2 < by; dy2++ ) { + for ( int dx2 = 0; block && dx2 < bx; dx2++ ) { cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); - if ( !*cpdiff ) - { + if ( !*cpdiff ) { block = false; } } } } } - if ( !block ) - { + if ( !block ) { *pdiff = BLACK; continue; } @@ -333,24 +307,20 @@ bool Zone::CheckAlarms( const Image *delta_image ) } } } - } - else - { + } else { alarm_filter_pixels = alarm_pixels; } - - if ( config.record_diag_images ) - { + + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 2 ); } diff_image->WriteJpeg( diag_path ); } - + Debug( 5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels ); - + if( alarm_filter_pixels ) { if( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { /* Not enough pixels alarmed */ @@ -364,14 +334,13 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No filtered pixels */ return (false); } - + score = (100*alarm_filter_pixels)/(polygon.Area()); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); - if ( check_method >= BLOBS ) - { + if ( check_method >= BLOBS ) { Debug( 5, "Checking for blob pixels" ); typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; BlobStats blob_stats[256]; @@ -380,44 +349,38 @@ bool Zone::CheckAlarms( const Image *delta_image ) uint8_t last_x, last_y; BlobStats *bsx, *bsy; BlobStats *bsm, *bss; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { + if ( *pdiff == WHITE ) { Debug( 9, "Got white pixel at %d,%d (%p)", x, y, pdiff ); //last_x = (x>lo_x)?*(pdiff-1):0; //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; - + last_x = 0; if(x > 0) { if((x-1) >= lo_x) { last_x = *(pdiff-1); } } - + last_y = 0; if(y > 0) { if((y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x) { last_y = *(pdiff-diff_width); } } - - if ( last_x ) - { + + if ( last_x ) { Debug( 9, "Left neighbour is %d", last_x ); bsx = &blob_stats[last_x]; - if ( last_y ) - { + if ( last_y ) { Debug( 9, "Top neighbour is %d", last_y ); bsy = &blob_stats[last_y]; - if ( last_x == last_y ) - { + if ( last_x == last_y ) { Debug( 9, "Matching neighbours, setting to %d", last_x ); // Add to the blob from the x side (either side really) *pdiff = last_x; @@ -425,9 +388,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bsx->count++; if ( x > bsx->hi_x ) bsx->hi_x = x; if ( (int)y > bsx->hi_y ) bsx->hi_y = y; - } - else - { + } else { // Aggregate blobs bsm = bsx->count>=bsy->count?bsx:bsy; bss = bsm==bsx?bsy:bsx; @@ -437,19 +398,16 @@ bool Zone::CheckAlarms( const Image *delta_image ) Debug( 9, "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); // Now change all those pixels to the other setting int changed = 0; - for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) - { + for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) { int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x; int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x; Debug( 9, "Changing %d, %d->%d", sy, lo_sx, hi_sx ); Debug( 9, "Range %d, %d->%d", sy, ranges[sy].lo_x, ranges[sy].hi_x ); spdiff = diff_buff + ((diff_width * sy) + lo_sx); - for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) - { + for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) { Debug( 9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff ); - if ( *spdiff == bss->tag ) - { + if ( *spdiff == bss->tag ) { Debug( 9, "Setting pixel" ); *spdiff = bsm->tag; changed++; @@ -458,8 +416,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } *pdiff = bsm->tag; alarm_blob_pixels++; - if ( !changed ) - { + if ( !changed ) { Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); Info( "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); Error( "No pixels changed, exiting" ); @@ -487,9 +444,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bss->hi_x = 0; bss->hi_y = 0; } - } - else - { + } else { Debug( 9, "Setting to left neighbour %d", last_x ); // Add to the blob from the x side *pdiff = last_x; @@ -498,14 +453,11 @@ bool Zone::CheckAlarms( const Image *delta_image ) if ( x > bsx->hi_x ) bsx->hi_x = x; if ( (int)y > bsx->hi_y ) bsx->hi_y = y; } - } - else - { - if ( last_y ) - { + } else { + if ( last_y ) { Debug( 9, "Top neighbour is %d", last_y ); Debug( 9, "Setting to top neighbour %d", last_y ); - + // Add to the blob from the y side BlobStats *bsy = &blob_stats[last_y]; @@ -514,28 +466,19 @@ bool Zone::CheckAlarms( const Image *delta_image ) bsy->count++; if ( x > bsy->hi_x ) bsy->hi_x = x; if ( (int)y > bsy->hi_y ) bsy->hi_y = y; - } - else - { + } else { // Create a new blob int i; - for ( i = (WHITE-1); i > 0; i-- ) - { + for ( i = (WHITE-1); i > 0; i-- ) { BlobStats *bs = &blob_stats[i]; // See if we can recycle one first, only if it's at least two rows up - if ( bs->count && bs->hi_y < (int)(y-1) ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + if ( bs->count && bs->hi_y < (int)(y-1) ) { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { + if ( config.create_analysis_images || config.record_diag_images ) { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { *spdiff = BLACK; } } @@ -543,7 +486,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_blobs--; alarm_blob_pixels -= bs->count; - + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); bs->tag = 0; @@ -554,8 +497,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bs->hi_y = 0; } } - if ( !bs->count ) - { + if ( !bs->count ) { Debug( 9, "Creating new blob %d", i ); *pdiff = i; alarm_blob_pixels++; @@ -569,8 +511,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) break; } } - if ( i == 0 ) - { + if ( i == 0 ) { Warning( "Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive." ); x = hi_x+1; y = hi_y+1; @@ -580,40 +521,30 @@ bool Zone::CheckAlarms( const Image *delta_image ) } } } - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 3 ); } diff_image->WriteJpeg( diag_path ); } - if ( !alarm_blobs ) - { + if ( !alarm_blobs ) { return( false ); } - + Debug( 5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); // Now eliminate blobs under the threshold - for ( int i = 1; i < WHITE; i++ ) - { + for ( int i = 1; i < WHITE; i++ ) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + if ( bs->count ) { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { + if ( config.create_analysis_images || config.record_diag_images ) { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { *spdiff = BLACK; } } @@ -621,7 +552,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_blobs--; alarm_blob_pixels -= bs->count; - + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); bs->tag = 0; @@ -630,26 +561,22 @@ bool Zone::CheckAlarms( const Image *delta_image ) bs->lo_y = 0; bs->hi_x = 0; bs->hi_y = 0; - } - else - { + } else { Debug( 6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count; if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count; } } } - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 4 ); } diff_image->WriteJpeg( diag_path ); } Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); - + if( alarm_blobs ) { if( min_blobs && (alarm_blobs < min_blobs) ) { /* Not enough pixels alarmed */ @@ -663,9 +590,9 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No blobs */ return (false); } - + score = (100*alarm_blob_pixels)/(polygon.Area()); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); @@ -673,25 +600,19 @@ bool Zone::CheckAlarms( const Image *delta_image ) alarm_hi_x = polygon.LoX()-1; alarm_lo_y = polygon.HiY()+1; alarm_hi_y = polygon.LoY()-1; - for ( int i = 1; i < WHITE; i++ ) - { + + for ( int i = 1; i < WHITE; i++ ) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( bs->count == max_blob_size ) - { - if ( config.weighted_alarm_centres ) - { + if ( bs->count ) { + if ( bs->count == max_blob_size ) { + if ( config.weighted_alarm_centres ) { unsigned long x_total = 0; unsigned long y_total = 0; - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { x_total += sx; y_total += sy; } @@ -699,9 +620,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_mid_x = int(round(x_total/bs->count)); alarm_mid_y = int(round(y_total/bs->count)); - } - else - { + } else { alarm_mid_x = int((bs->hi_x+bs->lo_x+1)/2); alarm_mid_y = int((bs->hi_y+bs->lo_y+1)/2); } @@ -711,115 +630,91 @@ bool Zone::CheckAlarms( const Image *delta_image ) if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y; if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x; if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y; - } - } - } - else - { + } // end if bs->count + } // end for i < WHITE + } else { alarm_mid_x = int((alarm_hi_x+alarm_lo_x+1)/2); alarm_mid_y = int((alarm_hi_y+alarm_lo_y+1)/2); } } - if ( type == INCLUSIVE ) - { + if ( type == INCLUSIVE ) { // score >>= 1; score /= 2; - } - else if ( type == EXCLUSIVE ) - { + } else if ( type == EXCLUSIVE ) { // score <<= 1; score *= 2; - } Debug( 5, "Adjusted score is %d", score ); // Now outline the changed region - if ( score ) - { + if ( score ) { alarm_box = Box( Coord( alarm_lo_x, alarm_lo_y ), Coord( alarm_hi_x, alarm_hi_y ) ); //if ( monitor->followMotion() ) - if ( true ) - { + if ( true ) { alarm_centre = Coord( alarm_mid_x, alarm_mid_y ); - } - else - { + } else { alarm_centre = alarm_box.Centre(); } - if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) - { + if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) { // First mask out anything we don't want - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { pdiff = diff_buff + ((diff_width * y) + lo_x); int lo_x2 = ranges[y].lo_x; int hi_x2 = ranges[y].hi_x; int lo_gap = lo_x2-lo_x; - if ( lo_gap > 0 ) - { - if ( lo_gap == 1 ) - { + if ( lo_gap > 0 ) { + if ( lo_gap == 1 ) { *pdiff++ = BLACK; - } - else - { + } else { memset( pdiff, BLACK, lo_gap ); pdiff += lo_gap; } } ppoly = pg_image->Buffer( lo_x2, y ); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) - { - if ( !*ppoly ) - { + for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) { + if ( !*ppoly ) { *pdiff = BLACK; } } int hi_gap = hi_x-hi_x2; - if ( hi_gap > 0 ) - { - if ( hi_gap == 1 ) - { + if ( hi_gap > 0 ) { + if ( hi_gap == 1 ) { *pdiff = BLACK; - } - else - { + } else { memset( pdiff, BLACK, hi_gap ); } } } - - if( monitor->Colours() == ZM_COLOUR_GRAY8 ) { + + if ( monitor->Colours() == ZM_COLOUR_GRAY8 ) { image = diff_image->HighlightEdges( alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent() ); } else { image = diff_image->HighlightEdges( alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent() ); } - + // Only need to delete this when 'image' becomes detached and points somewhere else delete diff_image; - } - else - { + } else { delete image; image = 0; } - Debug( 1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); + Debug( 1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", + Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); } return( true ); } -bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) -{ +bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) { Debug( 3, "Parsing polygon string '%s'", poly_string ); char *str_ptr = new char[strlen(poly_string)+1]; @@ -830,27 +725,21 @@ bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) int n_coords = 0; int max_n_coords = strlen(str)/4; Coord *coords = new Coord[max_n_coords]; - while( true ) - { - if ( *str == '\0' ) - { + while( true ) { + if ( *str == '\0' ) { break; } ws = strchr( str, ' ' ); - if ( ws ) - { + if ( ws ) { *ws = '\0'; } char *cp = strchr( str, ',' ); - if ( !cp ) - { + if ( !cp ) { Error( "Bogus coordinate %s found in polygon string", str ); delete[] coords; delete[] str_ptr; return( false ); - } - else - { + } else { *cp = '\0'; char *xp = str; char *yp = cp+1; @@ -888,8 +777,7 @@ bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) return( true ); } -bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ) -{ +bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ) { Debug( 3, "Parsing zone string '%s'", zone_string ); char *str_ptr = new char[strlen(zone_string)+1]; @@ -897,14 +785,12 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, strcpy( str, zone_string ); char *ws = strchr( str, ' ' ); - if ( !ws ) - { + if ( !ws ) { Debug( 3, "No initial whitespace found in zone string '%s', finishing", str ); } zone_id = strtol( str, 0, 10 ); Debug( 3, "Got zone %d from zone string", zone_id ); - if ( !ws ) - { + if ( !ws ) { delete str_ptr; return( true ); } @@ -913,14 +799,12 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, str = ws+1; ws = strchr( str, ' ' ); - if ( !ws ) - { + if ( !ws ) { Debug( 3, "No secondary whitespace found in zone string '%s', finishing", zone_string ); } colour = strtol( str, 0, 16 ); Debug( 3, "Got colour %06x from zone string", colour ); - if ( !ws ) - { + if ( !ws ) { delete str_ptr; return( true ); } @@ -937,19 +821,16 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, return( result ); } -int Zone::Load( Monitor *monitor, Zone **&zones ) -{ +int Zone::Load( Monitor *monitor, Zone **&zones ) { static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id() ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -957,8 +838,7 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) Debug( 1, "Got %d zones for monitor %s", n_zones, monitor->Name() ); delete[] zones; zones = new Zone *[n_zones]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int Id = atoi(dbrow[col++]); @@ -982,7 +862,7 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) int MaxBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; int OverloadFrames = dbrow[col]?atoi(dbrow[col]):0; col++; int ExtendAlarmFrames = dbrow[col]?atoi(dbrow[col]):0; col++; - + /* HTML colour code is actually BGR in memory, we want RGB */ AlarmRGB = rgb_convert(AlarmRGB, ZM_SUBPIX_ORDER_BGR); @@ -990,17 +870,18 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) Polygon polygon; if ( !ParsePolygonString( Coords, polygon ) ) { Error( "Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name() ); + n_zones -= 1; continue; } if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() - || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { + || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { Error( "Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY() ); + n_zones -= 1; continue; } - if ( false && !strcmp( Units, "Percent" ) ) - { + if ( false && !strcmp( Units, "Percent" ) ) { MinAlarmPixels = (MinAlarmPixels*polygon.Area())/100; MaxAlarmPixels = (MaxAlarmPixels*polygon.Area())/100; MinFilterPixels = (MinFilterPixels*polygon.Area())/100; @@ -1009,53 +890,44 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; } - if ( atoi(dbrow[2]) == Zone::INACTIVE ) - { + if ( atoi(dbrow[2]) == Zone::INACTIVE ) { zones[i] = new Zone( monitor, Id, Name, polygon ); + } else if ( atoi(dbrow[2]) == Zone::PRIVACY ) { + zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon ); } - else if ( atoi(dbrow[2]) == Zone::PRIVACY ) - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon ); - } - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); - } + zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - // Yadda yadda mysql_free_result( result ); return( n_zones ); } -bool Zone::DumpSettings( char *output, bool /*verbose*/ ) -{ +bool Zone::DumpSettings( char *output, bool /*verbose*/ ) { output[0] = 0; sprintf( output+strlen(output), " Id : %d\n", id ); sprintf( output+strlen(output), " Label : %s\n", label ); sprintf( output+strlen(output), " Type: %d - %s\n", type, - type==ACTIVE?"Active":( - type==INCLUSIVE?"Inclusive":( - type==EXCLUSIVE?"Exclusive":( - type==PRECLUSIVE?"Preclusive":( - type==INACTIVE?"Inactive":( - type==PRIVACY?"Privacy":"Unknown" - )))))); + type==ACTIVE?"Active":( + type==INCLUSIVE?"Inclusive":( + type==EXCLUSIVE?"Exclusive":( + type==PRECLUSIVE?"Preclusive":( + type==INACTIVE?"Inactive":( + type==PRIVACY?"Privacy":"Unknown" + )))))); sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); - for ( int i = 0; i < polygon.getNumCoords(); i++ ) - { + for ( int i = 0; i < polygon.getNumCoords(); i++ ) { sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); } sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, - check_method==ALARMED_PIXELS?"Alarmed Pixels":( - check_method==FILTERED_PIXELS?"FilteredPixels":( - check_method==BLOBS?"Blobs":"Unknown" - ))); + check_method==ALARMED_PIXELS?"Alarmed Pixels":( + check_method==FILTERED_PIXELS?"FilteredPixels":( + check_method==BLOBS?"Blobs":"Unknown" + ))); sprintf( output+strlen(output), " Min Pixel Threshold : %d\n", min_pixel_threshold ); sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); @@ -1080,38 +952,33 @@ void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsig unsigned int hi_y; unsigned int lo_x; unsigned int hi_x; - + if(max_pixel_threshold) calc_max_pixel_threshold = max_pixel_threshold; - + lo_y = polygon.LoY(); hi_y = polygon.HiY(); - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { lo_x = ranges[y].lo_x; hi_x = ranges[y].hi_x; - + Debug( 7, "Checking line %d from %d -> %d", y, lo_x, hi_x ); pdiff = (uint8_t*)pdiff_image->Buffer( lo_x, y ); ppoly = ppoly_image->Buffer( lo_x, y ); - - for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) - { - if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) - { + + for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) { + if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) { pixelsalarmed++; pixelsdifference += *pdiff; *pdiff = WHITE; - } - else - { + } else { *pdiff = BLACK; } } } - + /* Store the results */ *pixel_count = pixelsalarmed; *pixel_sum = pixelsdifference; - Debug( 7, "STORED"); + Debug( 7, "STORED"); } diff --git a/src/zma.cpp b/src/zma.cpp index f9557ba59..e59a0e65d 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -133,7 +133,7 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor *monitor = Monitor::Load( id, true, Monitor::ANALYSIS ); @@ -141,11 +141,6 @@ int main( int argc, char *argv[] ) { Info( "In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled() ); - if ( config.opt_frame_server ) - { - Event::OpenFrameSocket( monitor->Id() ); - } - zmSetDefaultHupHandler(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -168,7 +163,7 @@ int main( int argc, char *argv[] ) if ( analysis_update_delay ) { cur_time = time( 0 ); - if ( ( cur_time - last_analysis_update_time ) > analysis_update_delay ) + if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) { analysis_rate = monitor->GetAnalysisRate(); monitor->UpdateAdaptiveSkip(); diff --git a/src/zmc.cpp b/src/zmc.cpp index 288fc0d7e..5c4a01da2 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -27,7 +27,6 @@ zmc - The ZoneMinder Capture daemon zmc -d zmc --device - zmc -r -H -P -p zmc -f zmc --file zmc -m @@ -45,7 +44,6 @@ possible, this should run at more or less constant speed. =head1 OPTIONS -d, --device - For local cameras, device to access. e.g /dev/video0 etc - -r -H -P -p - For remote cameras -f, --file - For local images, jpg file to access. -m, --monitor_id - ID of the monitor to analyse -h, --help - Display usage information @@ -73,8 +71,7 @@ possible, this should run at more or less constant speed. #include "zm_signal.h" #include "zm_monitor.h" -void Usage() -{ +void Usage() { fprintf( stderr, "zmc -d or -r -H -P -p or -f or -m \n" ); fprintf( stderr, "Options:\n" ); @@ -83,7 +80,6 @@ void Usage() #else fprintf( stderr, " -d, --device : For local cameras, device to access. E.g /dev/video0 etc\n" ); #endif - fprintf( stderr, " -r -H -P -p : For remote cameras\n" ); fprintf( stderr, " -f, --file : For local images, jpg file to access.\n" ); fprintf( stderr, " -m, --monitor : For sources associated with a single monitor\n" ); fprintf( stderr, " -h, --help : This screen\n" ); @@ -91,8 +87,7 @@ void Usage() exit( 0 ); } -int main( int argc, char *argv[] ) -{ +int main( int argc, char *argv[] ) { self = argv[0]; srand( getpid() * time( 0 ) ); @@ -110,21 +105,19 @@ int main( int argc, char *argv[] ) {"protocol", 1, 0, 'r'}, {"host", 1, 0, 'H'}, {"port", 1, 0, 'P'}, - {"path", 1, 0, 'p'}, - {"file", 1, 0, 'f'}, + {"path", 1, 0, 'p'}, + {"file", 1, 0, 'f'}, {"monitor", 1, 0, 'm'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, {0, 0, 0, 0} }; - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:H:P:p:f:m:h:v", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } @@ -161,8 +154,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) printf ("%s ", argv[optind++]); @@ -171,37 +163,28 @@ int main( int argc, char *argv[] ) } int modes = ( device[0]?1:0 + host[0]?1:0 + file[0]?1:0 + (monitor_id>0?1:0) ); - if ( modes > 1 ) - { + if ( modes > 1 ) { fprintf( stderr, "Only one of device, host/port/path, file or monitor id allowed\n" ); Usage(); exit( 0 ); } - if ( modes < 1 ) - { + if ( modes < 1 ) { fprintf( stderr, "One of device, host/port/path, file or monitor id must be specified\n" ); Usage(); exit( 0 ); } char log_id_string[32] = ""; - if ( device[0] ) - { + if ( device[0] ) { const char *slash_ptr = strrchr( device, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_d%s", slash_ptr?slash_ptr+1:device ); - } - else if ( host[0] ) - { + } else if ( host[0] ) { snprintf( log_id_string, sizeof(log_id_string), "zmc_h%s", host ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { const char *slash_ptr = strrchr( file, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_f%s", slash_ptr?slash_ptr+1:file ); - } - else - { + } else { snprintf( log_id_string, sizeof(log_id_string), "zmc_m%d", monitor_id ); } @@ -209,40 +192,31 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor **monitors = 0; int n_monitors = 0; #if ZM_HAS_V4L - if ( device[0] ) - { + if ( device[0] ) { n_monitors = Monitor::LoadLocalMonitors( device, monitors, Monitor::CAPTURE ); - } - else + } else #endif // ZM_HAS_V4L - if ( host[0] ) - { + if ( host[0] ) { if ( !port ) port = "80"; n_monitors = Monitor::LoadRemoteMonitors( protocol, host, port, path, monitors, Monitor::CAPTURE ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { n_monitors = Monitor::LoadFileMonitors( file, monitors, Monitor::CAPTURE ); - } - else - { + } else { Monitor *monitor = Monitor::Load( monitor_id, true, Monitor::CAPTURE ); - if ( monitor ) - { + if ( monitor ) { monitors = new Monitor *[1]; monitors[0] = monitor; n_monitors = 1; } } - if ( !n_monitors ) - { + if ( !n_monitors ) { Error( "No monitors found" ); exit ( -1 ); } @@ -258,8 +232,7 @@ int main( int argc, char *argv[] ) sigaddset( &block_set, SIGUSR1 ); sigaddset( &block_set, SIGUSR2 ); - if ( monitors[0]->PrimeCapture() < 0 ) - { + if ( monitors[0]->PrimeCapture() < 0 ) { Error( "Failed to prime capture of initial monitor" ); exit( -1 ); } @@ -268,8 +241,7 @@ int main( int argc, char *argv[] ) long *alarm_capture_delays = new long[n_monitors]; long *next_delays = new long[n_monitors]; struct timeval * last_capture_times = new struct timeval[n_monitors]; - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; 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(); @@ -278,18 +250,14 @@ int main( int argc, char *argv[] ) int result = 0; struct timeval now; struct DeltaTimeval delta_time; - while( !zm_terminate ) - { + while( !zm_terminate ) { sigprocmask( SIG_BLOCK, &block_set, 0 ); - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; i++ ) { long min_delay = MAXINT; gettimeofday( &now, NULL ); - for ( int j = 0; j < n_monitors; j++ ) - { - if ( last_capture_times[j].tv_sec ) - { + for ( int j = 0; j < n_monitors; j++ ) { + if ( last_capture_times[j].tv_sec ) { DELTA_TIMEVAL( delta_time, now, last_capture_times[j], DT_PREC_3 ); if ( monitors[i]->GetState() == Monitor::ALARM ) next_delays[j] = alarm_capture_delays[j]-delta_time.delta; @@ -297,58 +265,49 @@ int main( int argc, char *argv[] ) next_delays[j] = capture_delays[j]-delta_time.delta; if ( next_delays[j] < 0 ) next_delays[j] = 0; - } - else - { + } else { next_delays[j] = 0; } - if ( next_delays[j] <= min_delay ) - { + if ( next_delays[j] <= min_delay ) { min_delay = next_delays[j]; } } - if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) - { - if ( monitors[i]->PreCapture() < 0 ) - { + if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { + if ( monitors[i]->PreCapture() < 0 ) { Error( "Failed to pre-capture monitor %d %d (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->Capture() < 0 ) - { + if ( monitors[i]->Capture() < 0 ) { Error( "Failed to capture image from monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->PostCapture() < 0 ) - { + if ( monitors[i]->PostCapture() < 0 ) { Error( "Failed to post-capture monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( next_delays[i] > 0 ) - { + if ( next_delays[i] > 0 ) { gettimeofday( &now, NULL ); DELTA_TIMEVAL( delta_time, now, last_capture_times[i], DT_PREC_3 ); long sleep_time = next_delays[i]-delta_time.delta; - if ( sleep_time > 0 ) - { + if ( sleep_time > 0 ) { usleep( sleep_time*(DT_MAXGRAN/DT_PREC_3) ); } } gettimeofday( &(last_capture_times[i]), NULL ); - } - } + } // end if next_delay <= min_delay || next_delays[i] <= 0 ) + + } // end foreach n_monitors sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - for ( int i = 0; i < n_monitors; i++ ) - { + } // end while ! zm_terminate + for ( int i = 0; i < n_monitors; i++ ) { delete monitors[i]; } delete [] monitors; diff --git a/src/zmf.cpp b/src/zmf.cpp deleted file mode 100644 index 2245d9ba3..000000000 --- a/src/zmf.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// -// ZoneMinder Image File Writer Implementation, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -/* - -=head1 NAME - -zmf - The ZoneMinder Frame daemon - -=head1 SYNOPSIS - - zmf -m - zmf --monitor - zmf -h - zmf --help - zmf -v - zmf --version - -=head1 DESCRIPTION - -This is an optional daemon that can run in concert with the Analysis daemon and -whose function it is to actually write captured frames to disk. This frees up -the Analysis daemon to do more analysis (!) and so keep up with the Capture -daemon better. If it isn't running or dies then the Analysis daemon just writes -them itself. - -=head1 OPTIONS - - -m, --monitor_id - ID of the monitor to use - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_signal.h" -#include "zm_monitor.h" - -#include "zmf.h" - -int OpenSocket( int monitor_id ) -{ - int sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { - Error( "Can't create socket: %s", strerror(errno) ); - return( -1 ); - } - - char sock_path[PATH_MAX] = ""; - snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); - if ( unlink( sock_path ) < 0 ) - { - Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) ); - } - - struct sockaddr_un addr; - - strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); - addr.sun_family = AF_UNIX; - - if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 ) - { - Error( "Can't bind: %s", strerror(errno) ); - exit( -1 ); - } - - if ( listen( sd, SOMAXCONN ) < 0 ) - { - Error( "Can't listen: %s", strerror(errno) ); - return( -1 ); - } - - struct sockaddr_un rem_addr; - socklen_t rem_addr_len = sizeof(rem_addr); - int new_sd = -1; - if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 ) - { - Error( "Can't accept: %s", strerror(errno) ); - exit( -1 ); - } - close( sd ); - - sd = new_sd; - - Info( "Frame server socket open, awaiting images" ); - return( sd ); -} - -int ReopenSocket( int &sd, int monitor_id ) -{ - close( sd ); - return( sd = OpenSocket( monitor_id ) ); -} - -void Usage() -{ - fprintf( stderr, "zmf -m \n" ); - fprintf( stderr, "Options:\n" ); - fprintf( stderr, " -m, --monitor : Specify which monitor to use\n" ); - fprintf( stderr, " -h, --help : This screen\n" ); - fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" ); - exit( 0 ); -} - -int main( int argc, char *argv[] ) -{ - self = argv[0]; - - srand( getpid() * time( 0 ) ); - - int id = -1; - - static struct option long_options[] = { - {"monitor", 1, 0, 'm'}, - {"help", 0, 0, 'h'}, - {"version", 0, 0, 'v'}, - {0, 0, 0, 0} - }; - - while (1) - { - int option_index = 0; - - int c = getopt_long (argc, argv, "m:h:v", long_options, &option_index); - if (c == -1) - { - break; - } - - switch (c) - { - case 'm': - id = atoi(optarg); - break; - case 'h': - case '?': - Usage(); - break; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - default: - //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); - break; - } - } - - if (optind < argc) - { - fprintf( stderr, "Extraneous options, " ); - while (optind < argc) - printf ("%s ", argv[optind++]); - printf ("\n"); - Usage(); - } - - if ( id < 0 ) - { - fprintf( stderr, "Bogus monitor %d\n", id ); - Usage(); - exit( 0 ); - } - - char log_id_string[16]; - snprintf( log_id_string, sizeof(log_id_string), "m%d", id ); - - zmLoadConfig(); - - logInit( "zmf" ); - - ssedetect(); - - Monitor *monitor = Monitor::Load( id, false, Monitor::QUERY ); - - if ( !monitor ) - { - fprintf( stderr, "Can't find monitor with id of %d\n", id ); - exit( -1 ); - } - - char capt_path[PATH_MAX]; - char anal_path[PATH_MAX]; - snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); - snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); - - sigset_t block_set; - sigemptyset( &block_set ); - - int sd = OpenSocket( monitor->Id() ); - - FrameHeader frame_header = { 0, 0, false, 0 }; - //unsigned char *image_data = 0; - - fd_set rfds; - - struct timeval timeout; - timeout.tv_sec = 1; - timeout.tv_usec = 0; - while( 1 ) - { - struct timeval temp_timeout = timeout; - - FD_ZERO(&rfds); - FD_SET(sd, &rfds); - int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout ); - if( n_found == 0 ) - { - Debug( 1, "Select timed out" ); - continue; - } - else if ( n_found < 0) - { - Error( "Select error: %s", strerror(errno) ); - ReopenSocket( sd, monitor->Id() ); - continue; - } - - sigprocmask( SIG_BLOCK, &block_set, 0 ); - - int n_bytes = read( sd, &frame_header, sizeof(frame_header) ); - if ( n_bytes != sizeof(frame_header) ) - { - if ( n_bytes < 0 ) - { - Error( "Can't read frame header: %s", strerror(errno) ); - } - else if ( n_bytes > 0 ) - { - Error( "Incomplete read of frame header, %d bytes only", n_bytes ); - } - else - { - Warning( "Socket closed at remote end" ); - } - ReopenSocket( sd, monitor->Id() ); - continue; - } - Debug( 1, "Read frame header, expecting %ld bytes of image", frame_header.image_length ); - static unsigned char image_data[ZM_MAX_IMAGE_SIZE]; - - // Read for pipe and loop until bytes expected have been read or an error occurs - int bytes_read = 0; - do - { - n_bytes = read( sd, image_data+bytes_read, frame_header.image_length-bytes_read ); - if (n_bytes < 0) break; // break on error - if (n_bytes < (int)frame_header.image_length) - { - // print some informational messages - if (bytes_read == 0) - { - Debug(4,"Image read : Short read %d bytes of %d expected bytes",n_bytes,frame_header.image_length); - } - else if (bytes_read+n_bytes == (int)frame_header.image_length) - { - Debug(5,"Image read : Read rest of short read: %d bytes read total of %d bytes",n_bytes,frame_header.image_length); - } - else - { - Debug(6,"Image read : continuing, read %d bytes (%d so far)", n_bytes, bytes_read+n_bytes); - } - } - bytes_read+= n_bytes; - } while (n_bytes>0 && (bytes_read < (ssize_t)frame_header.image_length) ); - - // Print errors if there was a problem - if ( n_bytes < 1 ) - { - Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length); - if ( n_bytes < 0 ) - { - Error( "Can't read frame image data: %s", strerror(errno) ); - } - else - { - Warning( "Socket closed at remote end" ); - } - ReopenSocket( sd, monitor->Id() ); - continue; - } - - static char subpath[PATH_MAX] = ""; - if ( config.use_deep_storage ) - { - struct tm *time = localtime( &frame_header.event_time ); - snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); - } - else - { - snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id ); - } - - static char path[PATH_MAX] = ""; - snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id ); - Debug( 1, "Got image, writing to %s", path ); - - FILE *fd = 0; - if ( (fd = fopen( path, "w" )) < 0 ) - { - Error( "Can't fopen '%s': %s", path, strerror(errno) ); - exit( -1 ); - } - if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) ) - { - Error( "Can't fwrite image data: %s", strerror(errno) ); - exit( -1 ); - } - fclose( fd ); - - sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - logTerm(); - zmDbClose(); -} diff --git a/src/zmf.h b/src/zmf.h deleted file mode 100644 index 9d5bab127..000000000 --- a/src/zmf.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ZoneMinder Image File Write Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZMFILE_H -#define ZMFILE_H - -struct FrameHeader -{ - unsigned long event_id; - time_t event_time; - unsigned long frame_id; - bool alarm_frame; - unsigned long image_length; -}; - -#endif // ZMFILE_H diff --git a/src/zms.cpp b/src/zms.cpp index 87eaf3202..67f0e3cab 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -69,8 +69,8 @@ int main( int argc, const char *argv[] ) unsigned int bitrate = 100000; unsigned int ttl = 0; EventStream::StreamMode replay = EventStream::MODE_SINGLE; - char username[64] = ""; - char password[64] = ""; + std::string username; + std::string password; char auth[64] = ""; unsigned int connkey = 0; unsigned int playback_buffer = 0; @@ -91,7 +91,7 @@ int main( int argc, const char *argv[] ) logInit( "zms" ); - ssedetect(); + hwcaps_detect(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -164,7 +164,7 @@ int main( int argc, const char *argv[] ) { if ( !strcmp( name, "user" ) ) { - strncpy( username, value, sizeof(username) ); + username = value; } } else @@ -180,11 +180,11 @@ int main( int argc, const char *argv[] ) { if ( !strcmp( name, "user" ) ) { - strncpy( username, value, sizeof(username) ); + username = UriDecode( value ); } if ( !strcmp( name, "pass" ) ) { - strncpy( password, value, sizeof(password) ); + password = UriDecode( value ); } } } @@ -198,9 +198,9 @@ int main( int argc, const char *argv[] ) if ( strcmp( config.auth_relay, "none" ) == 0 ) { - if ( *username ) + if ( username.length() ) { - user = zmLoadUser( username ); + user = zmLoadUser( username.c_str() ); } } else @@ -214,9 +214,9 @@ int main( int argc, const char *argv[] ) } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { - if ( *username && *password ) + if ( username.length() && password.length() ) { - user = zmLoadUser( username, password ); + user = zmLoadUser( username.c_str(), password.c_str() ); } } } diff --git a/src/zmstreamer.cpp b/src/zmstreamer.cpp deleted file mode 100644 index 74cc01226..000000000 --- a/src/zmstreamer.cpp +++ /dev/null @@ -1,252 +0,0 @@ -// -// ZoneMinder Streamer, $Date: 2010-10-14 23:21:00 +0200 (Thu, 14 Oct 2010) $ -// Copyright (C) 2001-2010 Philip Coombes, Chris Kistner -// -// This program is based on revision 3143 of -// http://svn.zoneminder.com/svn/zm/trunk/src/zms.cpp -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -/* - -=head1 NAME - -zmstreamer - eyeZM video streamer - -=head1 SYNOPSIS - - zmstreamer -e - zmstreamer -o - zmstreamer -u - zmstreamer -f - zmstreamer -s - zmstreamer -b - zmstreamer -m - zmstreamer -d - zmstreamer -i - zmstreamer -? - zmstreamer -h - zmstreamer -v - -=head1 DESCRIPTION - -*DEPRECIATED* The xml skin and all files associated with the xml skin are now -depreciated. Please use the ZoneMinder API instead. - -This binary works in conjunction with the XML skin to stream video to iPhones -running the eyeZm app. - -=head1 OPTIONS - - -e - Specify output mode: mpeg/jpg/zip/single/raw. - -o - Specify output format. - -u - Specify buffer size in ms. - -f - Specify maximum framerate. - -s - Specify scale. - -b - Specify bitrate. - -m - Specify monitor id. - -d - 0 = off, 1 = no streaming, 2 = with streaming. - -i, -?, -h - Display usage information - -v - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include - -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_user.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_stream.h" - -// Possible command-line options -#define OPTIONS "e:o:u:f:s:b:m:d:i:?:h:v" - -// Default ZMS values -#define ZMS_DEFAULT_DEBUG 0 -#define ZMS_DEFAULT_ID 1 -#define ZMS_DEFAULT_BITRATE 100000 -#define ZMS_DEFAULT_SCALE 100 -#define ZMS_DEFAULT_MODE "mpeg" -#define ZMS_DEFAULT_FORMAT "asf" -#define ZMS_DEFAULT_FPS 25.0 -#define ZMS_DEFAULT_BUFFER 1000 - -int main(int argc, char** argv) { - self = argv[0]; - // Set initial values to the default values - int debug = ZMS_DEFAULT_DEBUG; - int id = ZMS_DEFAULT_ID; - int bitrate = ZMS_DEFAULT_BITRATE; - int scale = ZMS_DEFAULT_SCALE; - char mode[32]; - sprintf(mode, "%s", ZMS_DEFAULT_MODE); - char format[32]; - sprintf(format, "%s", ZMS_DEFAULT_FORMAT); - double maxfps = ZMS_DEFAULT_FPS; - int buffer = ZMS_DEFAULT_BUFFER; - - // Parse command-line options - int arg; - while ((arg = getopt(argc, argv, OPTIONS)) != -1) { - switch (arg) { - case 'e': - sprintf(mode, "%s", optarg); - break; - case 'o': - sprintf(format, "%s", optarg); - break; - case 'u': - buffer = atoi(optarg); - break; - case 'f': - maxfps = atof(optarg); - break; - case 's': - scale = atoi(optarg); - break; - case 'b': - bitrate = atoi(optarg); - break; - case 'm': - id = atoi(optarg); - break; - case 'd': - debug = atoi(optarg); - break; - case 'h': - case 'i': - case '?': - printf("-e : Specify output mode: mpeg/jpg/zip/single/raw. Default = %s\n", ZMS_DEFAULT_MODE); - printf("-o : Specify output format. Default = %s\n", ZMS_DEFAULT_FORMAT); - printf("-u : Specify buffer size in ms. Default = %d\n", ZMS_DEFAULT_BUFFER); - printf("-f : Specify maximum framerate. Default = %lf\n", ZMS_DEFAULT_FPS); - printf("-s : Specify scale. Default = %d\n", ZMS_DEFAULT_SCALE); - printf("-b : Specify bitrate. Default = %d\n", ZMS_DEFAULT_BITRATE); - printf("-m : Specify monitor id. Default = %d\n", ZMS_DEFAULT_ID); - printf("-d : 0 = off, 1 = no streaming, 2 = with streaming. Default = 0\n"); - printf("-i or -? or -h: This information\n"); - printf("-v : This installed version of ZoneMinder\n"); - return EXIT_SUCCESS; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - } - } - - // Set stream type - StreamBase::StreamType streamtype; - if (!strcasecmp("raw", mode)) - streamtype = MonitorStream::STREAM_RAW; - else if (!strcasecmp("mpeg", mode)) - streamtype = MonitorStream::STREAM_MPEG; - else if (!strcasecmp("jpg", mode)) - streamtype = MonitorStream::STREAM_JPEG; - else if (!strcasecmp("single", mode)) - streamtype = MonitorStream::STREAM_SINGLE; - else if (!strcasecmp("zip", mode)) - streamtype = MonitorStream::STREAM_ZIP; - else - streamtype = MonitorStream::STREAM_MPEG; - - if (debug) { - // Show stream parameters - printf("Stream parameters:\n"); - switch (streamtype) { - case MonitorStream::STREAM_MPEG: - printf("Output mode (-e) = %s\n", "mpeg"); - printf("Output format (-o) = %s\n", format); - break; - default: - printf("Output mode (-e) = %s\n", mode); - } - printf("Buffer size (-u) = %d ms\n", buffer); - printf("Maximum FPS (-f) = %lf FPS\n", maxfps); - printf("Scale (-s) = %d%%\n", scale); - printf("Bitrate (-b) = %d bps\n", bitrate); - printf("Monitor Id (-m) = %d\n", id); - } - - if (debug) { - // Set ZM debugger to print to stdout - printf("Setting up ZoneMinder debugger to print to stdout..."); - setenv("ZM_DBG_PRINT", "1", 1); - printf("Done.\n"); - } - - // Loading ZM configurations - printf("Loading ZoneMinder configurations..."); - zmLoadConfig(); - printf("Done.\n"); - - logInit("zmstreamer"); - - ssedetect(); - - // Setting stream parameters - MonitorStream stream; - stream.setStreamScale(scale); // default = 100 (scale) - stream.setStreamReplayRate(100); // default = 100 (rate) - stream.setStreamMaxFPS(maxfps); // default = 10 (maxfps) - if (debug) stream.setStreamTTL(1); - else stream.setStreamTTL(0); // default = 0 (ttl) - stream.setStreamQueue(0); // default = 0 (connkey) - stream.setStreamBuffer(buffer); // default = 0 (buffer) - stream.setStreamStart(id); // default = 0 (monitor_id) - stream.setStreamType(streamtype); - if (streamtype == MonitorStream::STREAM_MPEG) { -#if HAVE_LIBAVCODEC - if (debug) printf("HAVE_LIBAVCODEC is set\n"); - stream.setStreamFormat(format); // default = "" (format) - stream.setStreamBitrate(bitrate); // default = 100000 (bitrate) -#else - fprintf(stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return EXIT_FAILURE; -#endif - } - - if (debug != 1) { - if (debug) printf("Running stream..."); - - // Output headers - fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); - time_t now = time(0); - char date_string[64]; - strftime(date_string, sizeof (date_string) - 1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); - fprintf(stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"); - fprintf(stdout, "Last-Modified: %s\r\n", date_string); - fprintf(stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n"); - fprintf(stdout, "Cache-Control: post-check=0, pre-check=0\r\n"); - fprintf(stdout, "Pragma: no-cache\r\n"); - - // Run stream - stream.runStream(); - } - if (debug) printf("Done.\n"); - - logTerm(); - zmDbClose(); - - return (EXIT_SUCCESS); -} diff --git a/src/zmu.cpp b/src/zmu.cpp index 21b623c4b..c3a795ad4 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -95,8 +95,7 @@ Options for use with monitors: #include "zm_monitor.h" #include "zm_local_camera.h" -void Usage( int status=-1 ) -{ +void Usage( int status=-1 ) { fprintf( stderr, "zmu <-d device_path> [-v] [function] [-U -P]\n" ); fprintf( stderr, "zmu <-m monitor_id> [-v] [function] [-U -P]\n" ); fprintf( stderr, "General options:\n" ); @@ -167,48 +166,38 @@ typedef enum { ZMU_LIST = 0x10000000, } Function; -bool ValidateAccess( User *user, int mon_id, int function ) -{ +bool ValidateAccess( User *user, int mon_id, int function ) { bool allowed = true; - if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) - { + if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) { if ( user->getStream() < User::PERM_VIEW ) allowed = false; } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( user->getEvents() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) - { + if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) { if ( user->getMonitors() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) - { + if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { if ( user->getMonitors() < User::PERM_EDIT ) allowed = false; } - if ( mon_id > 0 ) - { - if ( !user->canAccess( mon_id ) ) - { + if ( mon_id > 0 ) { + if ( !user->canAccess( mon_id ) ) { allowed = false; } } - if ( !allowed ) - { + if ( !allowed ) { fprintf( stderr, "Error, insufficient privileges for requested action\n" ); exit( -1 ); } return( allowed ); } -int main( int argc, char *argv[] ) -{ - if ( access(ZM_CONFIG, R_OK) != 0 ) - { +int main( int argc, char *argv[] ) { + if ( access(ZM_CONFIG, R_OK) != 0 ) { fprintf( stderr, "Can't open %s: %s\n", ZM_CONFIG, strerror(errno) ); exit( -1 ); } @@ -274,18 +263,15 @@ int main( int argc, char *argv[] ) int v4lVersion = 1; #endif // ZM_HAS_V4L2/1 #endif // ZM_HAS_V4L - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } - switch (c) - { + switch (c) { case 'd': if ( optarg ) device = optarg; @@ -405,8 +391,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) fprintf( stderr, "%s ", argv[optind++]); @@ -414,13 +399,11 @@ int main( int argc, char *argv[] ) Usage(); } - if ( device && !(function&ZMU_QUERY) ) - { + if ( device && !(function&ZMU_QUERY) ) { fprintf( stderr, "Error, -d option cannot be used with this option\n" ); Usage(); } - if ( scale != -1 && !(function&ZMU_IMAGE) ) - { + if ( scale != -1 && !(function&ZMU_IMAGE) ) { fprintf( stderr, "Error, -S option cannot be used with this option\n" ); Usage(); } @@ -435,46 +418,36 @@ int main( int argc, char *argv[] ) User *user = 0; - if ( config.opt_use_auth ) - { - if ( strcmp( config.auth_relay, "none" ) == 0 ) - { - if ( !username ) - { + if ( config.opt_use_auth ) { + if ( strcmp( config.auth_relay, "none" ) == 0 ) { + if ( !username ) { fprintf( stderr, "Error, username must be supplied\n" ); exit( -1 ); } - if ( username ) - { + if ( username ) { user = zmLoadUser( username ); } - } - else - { - if ( !(username && password) && !auth ) - { + } else { + if ( !(username && password) && !auth ) { fprintf( stderr, "Error, username and password or auth string must be supplied\n" ); exit( -1 ); } //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) { - if ( auth ) - { + if ( auth ) { user = zmLoadAuthUser( auth, false ); } } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { - if ( username && password ) - { + if ( username && password ) { user = zmLoadUser( username, password ); } } } - if ( !user ) - { + if ( !user ) { fprintf( stderr, "Error, unable to authenticate user\n" ); exit( -1 ); } @@ -482,13 +455,10 @@ int main( int argc, char *argv[] ) } - if ( mon_id > 0 ) - { + if ( mon_id > 0 ) { Monitor *monitor = Monitor::Load( mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY ); - if ( monitor ) - { - if ( verbose ) - { + if ( monitor ) { + if ( verbose ) { printf( "Monitor %d(%s)\n", monitor->Id(), monitor->Name() ); } if ( ! monitor->connect() ) { @@ -498,23 +468,19 @@ int main( int argc, char *argv[] ) char separator = ' '; bool have_output = false; - if ( function & ZMU_STATE ) - { + if ( function & ZMU_STATE ) { Monitor::State state = monitor->GetState(); if ( verbose ) printf( "Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle") ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", state ); have_output = true; } } - if ( function & ZMU_TIME ) - { + if ( function & ZMU_TIME ) { struct timeval timestamp = monitor->GetTimestamp( image_idx ); - if ( verbose ) - { + if ( verbose ) { char timestamp_str[64] = "None"; if ( timestamp.tv_sec ) strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); @@ -522,62 +488,50 @@ int main( int argc, char *argv[] ) 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 ) printf( "%c", separator ); printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); have_output = true; } } - if ( function & ZMU_READ_IDX ) - { + if ( function & ZMU_READ_IDX ) { if ( verbose ) printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastReadIndex() ); have_output = true; } } - if ( function & ZMU_WRITE_IDX ) - { + if ( function & ZMU_WRITE_IDX ) { if ( verbose ) printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastWriteIndex() ); have_output = true; } } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( verbose ) printf( "Last event id: %d\n", monitor->GetLastEvent() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastEvent() ); have_output = true; } } - if ( function & ZMU_FPS ) - { + if ( function & ZMU_FPS ) { if ( verbose ) printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%.2f", monitor->GetFPS() ); have_output = true; } } - if ( function & ZMU_IMAGE ) - { - if ( verbose ) - { + if ( function & ZMU_IMAGE ) { + if ( verbose ) { if ( image_idx == -1 ) printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); else @@ -588,77 +542,63 @@ int main( int argc, char *argv[] ) } monitor->GetImage( image_idx, scale>0?scale:100 ); } - if ( function & ZMU_ZONES ) - { + if ( function & ZMU_ZONES ) { if ( verbose ) printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); monitor->DumpZoneImage( zoneString ); } - if ( function & ZMU_ALARM ) - { + if ( function & ZMU_ALARM ) { if ( verbose ) printf( "Forcing alarm on\n" ); monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); } - if ( function & ZMU_NOALARM ) - { + if ( function & ZMU_NOALARM ) { if ( verbose ) printf( "Forcing alarm off\n" ); monitor->ForceAlarmOff(); } - if ( function & ZMU_CANCEL ) - { + if ( function & ZMU_CANCEL ) { if ( verbose ) printf( "Cancelling forced alarm on/off\n" ); monitor->CancelForced(); } - if ( function & ZMU_RELOAD ) - { + if ( function & ZMU_RELOAD ) { if ( verbose ) printf( "Reloading monitor settings\n" ); monitor->actionReload(); } - if ( function & ZMU_ENABLE ) - { + if ( function & ZMU_ENABLE ) { if ( verbose ) printf( "Enabling event generation\n" ); monitor->actionEnable(); } - if ( function & ZMU_DISABLE ) - { + if ( function & ZMU_DISABLE ) { if ( verbose ) printf( "Disabling event generation\n" ); monitor->actionDisable(); } - if ( function & ZMU_SUSPEND ) - { + if ( function & ZMU_SUSPEND ) { if ( verbose ) printf( "Suspending event generation\n" ); monitor->actionSuspend(); } - if ( function & ZMU_RESUME ) - { + if ( function & ZMU_RESUME ) { if ( verbose ) printf( "Resuming event generation\n" ); monitor->actionResume(); } - if ( function & ZMU_QUERY ) - { + if ( function & ZMU_QUERY ) { char monString[16382] = ""; monitor->DumpSettings( monString, verbose ); printf( "%s\n", monString ); } - if ( function & ZMU_BRIGHTNESS ) - { - if ( verbose ) - { + if ( function & ZMU_BRIGHTNESS ) { + if ( verbose ) { if ( brightness >= 0 ) printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); else printf( "Current brightness: %d\n", monitor->actionBrightness() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( brightness >= 0 ) printf( "%d", monitor->actionBrightness( brightness ) ); @@ -667,17 +607,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_CONTRAST ) - { - if ( verbose ) - { + if ( function & ZMU_CONTRAST ) { + if ( verbose ) { if ( contrast >= 0 ) printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); else printf( "Current contrast: %d\n", monitor->actionContrast() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( contrast >= 0 ) printf( "%d", monitor->actionContrast( contrast ) ); @@ -686,17 +622,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_HUE ) - { - if ( verbose ) - { + if ( function & ZMU_HUE ) { + if ( verbose ) { if ( hue >= 0 ) printf( "New hue: %d\n", monitor->actionHue( hue ) ); else printf( "Current hue: %d\n", monitor->actionHue() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( hue >= 0 ) printf( "%d", monitor->actionHue( hue ) ); @@ -705,17 +637,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_COLOUR ) - { - if ( verbose ) - { + if ( function & ZMU_COLOUR ) { + if ( verbose ) { if ( colour >= 0 ) printf( "New colour: %d\n", monitor->actionColour( colour ) ); else printf( "Current colour: %d\n", monitor->actionColour() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( colour >= 0 ) printf( "%d", monitor->actionColour( colour ) ); @@ -724,26 +652,19 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( have_output ) - { + if ( have_output ) { printf( "\n" ); } - if ( !function ) - { + if ( !function ) { Usage(); } delete monitor; - } - else - { + } else { fprintf( stderr, "Error, invalid monitor id %d\n", mon_id ); exit( -1 ); } - } - else - { - if ( function & ZMU_QUERY ) - { + } else { + if ( function & ZMU_QUERY ) { #if ZM_HAS_V4L char vidString[0x10000] = ""; bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); @@ -755,24 +676,20 @@ int main( int argc, char *argv[] ) #endif // ZM_HAS_V4L } - if ( function & ZMU_LIST ) - { + if ( function & ZMU_LIST ) { std::string sql = "select Id, Function+0 from Monitors"; - if ( !verbose ) - { + if ( !verbose ) { sql += "where Function != 'None'"; } sql += " order by Id asc"; - if ( mysql_query( &dbconn, sql.c_str() ) ) - { + if ( mysql_query( &dbconn, sql.c_str() ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -780,17 +697,13 @@ int main( int argc, char *argv[] ) Debug( 1, "Got %d monitors", n_monitors ); printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int mon_id = atoi(dbrow[0]); int function = atoi(dbrow[1]); - if ( !user || user->canAccess( mon_id ) ) - { - if ( function > 1 ) - { + if ( !user || user->canAccess( mon_id ) ) { + if ( function > 1 ) { Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); - if ( monitor && monitor->connect() ) - { + if ( monitor && monitor->connect() ) { struct timeval tv = monitor->GetTimestamp(); printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", monitor->Id(), @@ -805,9 +718,7 @@ int main( int argc, char *argv[] ) ); delete monitor; } - } - else - { + } else { struct timeval tv = { 0, 0 }; printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 49c91acfc..554dd9c38 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -8,88 +8,272 @@ exit; fi +for i in "$@" +do +case $i in + -b=*|--branch=*) + BRANCH="${i#*=}" + shift # past argument=value + ;; + -d=*|--distro=*) + DISTRO="${i#*=}" + shift # past argument=value + ;; + -i=*|--interactive=*) + INTERACTIVE="${i#*=}" + shift # past argument=value + ;; + -r=*|--release=*) + RELEASE="${i#*=}" + shift + ;; + -s=*|--snapshot=*) + SNAPSHOT="${i#*=}" + shift # past argument=value + ;; + -t=*|--type=*) + TYPE="${i#*=}" + shift # past argument=value + ;; + -u=*|--urgency=*) + URGENCY="${i#*=}" + shift # past argument=value + ;; + -f=*|--fork=*) + GITHUB_FORK="${i#*=}" + shift # past argument=value + ;; + -v=*|--version=*) + PACKAGE_VERSION="${i#*=}" + shift + ;; + --default) + DEFAULT=YES + shift # past argument with no value + ;; + *) + # unknown option + read -p "Unknown option $i, continue? (Y|n)" + [[ $REPLY == [yY] ]] && { echo "continuing..."; } || exit 1; + ;; +esac +done DATE=`date -R` -DISTRO=$1 -SNAPSHOT=$2 -if [ "$SNAPSHOT" == "stable" ]; then -SNAPSHOT=""; -fi; - - -TYPE=$3 if [ "$TYPE" == "" ]; then -TYPE="source"; + echo "Defaulting to source build" + TYPE="source"; fi; -BRANCH=$4 + +if [ "$DISTRO" == "" ]; then + DISTRO=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; + echo "Defaulting to $DISTRO for distribution"; +else + echo "Building for $DISTRO"; +fi; + +# Release is a special mode... it uploads to the release ppa and cannot have a snapshot +if [ "$RELEASE" != "" ]; then + if [ "$SNAPSHOT" != "" ]; then + echo "Releases cannot have a snapshot.... exiting." + exit 0; + fi + if [ "$GITHUB_FORK" != "" ] && [ "$GITHUB_FORK" != "ZoneMinder" ]; then + echo "Releases cannot have a fork ($GITHUB_FORK).... exiting." + exit 0; + fi + BRANCH="release-$RELEASE" +else + if [ "$GITHUB_FORK" == "" ]; then + echo "Defaulting to ZoneMinder upstream git" + GITHUB_FORK="ZoneMinder" + fi; + if [ "$SNAPSHOT" == "stable" ]; then + if [ "$BRANCH" == "" ]; then + BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); + echo "Latest stable branch is $BRANCH"; + fi; + else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + fi; + fi; +fi -if [ ! -d 'zoneminder_release' ]; then - git clone https://github.com/ZoneMinder/ZoneMinder.git zoneminder_release +# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. +if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then + if [ -d "${GITHUB_FORK}_ZoneMinder.git" ]; then + echo "Using local clone ${GITHUB_FORK}_ZoneMinder.git to pull from." + cd "${GITHUB_FORK}_ZoneMinder.git" + echo "git pull..." + git pull + echo "git checkout $BRANCH" + git checkout $BRANCH + echo "git pull..." + git pull + cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + else + echo "git clone https://github.com/$GITHUB_FORK/ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "https://github.com/$GITHUB_FORK/ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + fi +else + echo "release dir already exists. Please remove it." + exit 0; fi; -if [ "$BRANCH" != "" ]; then - cd zoneminder_release - if [ "$BRANCH" == "stable" ]; then - BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); - echo "Latest stable branch is $BRANCH"; - - fi - git checkout $BRANCH - cd ../ + +cd "${GITHUB_FORK}_zoneminder_release" +if [ $RELEASE ]; then + git checkout $RELEASE +else + git checkout $BRANCH fi; -VERSION=`cat zoneminder_release/version` +cd ../ + +VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` + if [ $VERSION == "" ]; then exit 1; fi; -echo "Doing $TYPE release zoneminder_$VERSION-$DISTRO-$SNAPSHOT"; -mv zoneminder_release zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -git submodule init -git submodule update --init --recursive -if [ $DISTRO == "trusty" ]; then -ln -sf distros/ubuntu1204 debian -else -ln -sf distros/ubuntu1604 debian +if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then + VERSION="$VERSION~$SNAPSHOT"; fi; -# Auto-install all ZoneMinder's depedencies using the Debian control file -sudo apt-get install devscripts equivs -sudo mk-build-deps -ir ./debian/control +DIRECTORY="zoneminder_$VERSION"; +echo "Doing $TYPE release $DIRECTORY"; +mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig"; +cd "$DIRECTORY.orig"; -if [ -z `hostname -d` ] ; then - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" +git submodule init +git submodule update --init --recursive +if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then + mv distros/ubuntu1204 debian +else + if [ "$DISTRO" == "wheezy" ]; then + mv distros/debian debian + else + mv distros/ubuntu1604 debian + fi; +fi; + +if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then + AUTHOR="$DEBFULLNAME <$DEBEMAIL>" else - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + if [ -z `hostname -d` ] ; then + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" + else + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + fi fi +if [ "$URGENCY" = "" ]; then + URGENCY="medium" +fi; + +if [ "$SNAPSHOT" == "stable" ]; then cat < debian/changelog -zoneminder ($VERSION-$DISTRO-$SNAPSHOT) $DISTRO; urgency=medium +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * Release $VERSION + + -- $AUTHOR $DATE + +EOF +else +cat < debian/changelog +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * -- $AUTHOR $DATE EOF -#rm -rf .git -#rm .gitignore -#cd ../ -#tar zcf zoneminder_$VERSION-$DISTRO.orig.tar.gz zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -#cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig +fi; + +rm -rf .git +rm .gitignore +cd ../ +tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig +cd $DIRECTORY.orig + if [ $TYPE == "binary" ]; then - debuild + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir ./debian/control + echo "Status: $?" + DEBUILD=debuild else - if [ $TYPE == "local" ]; then - debuild -i -us -uc -b - else - debuild -S -sa - fi; + if [ $TYPE == "local" ]; then + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir ./debian/control + echo "Status: $?" + DEBUILD="debuild -i -us -uc -b" + else + DEBUILD="debuild -S -sa" + fi; +fi; +if [ "$DEBSIGN_KEYID" != "" ]; then + DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" +fi +$DEBUILD +if [ $? -ne 0 ]; then +echo "Error status code is: $?" + echo "Build failed."; + exit $?; fi; cd ../ +if [ "$INTERACTIVE" != "no" ]; then + read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" + [[ $REPLY == [yY] ]] && { mv $DIRECTORY zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr $DIRECTORY; echo "The checked out copy has been deleted"; } + echo "Done!" +else + rm -fr $DIRECTORY; echo "The checked out copy has been deleted"; +fi -read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" -[[ $REPLY == [yY] ]] && { mv zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig; echo "The checked out copy has been deleted"; } -echo "Done!" - +if [ $TYPE == "binary" ]; then + if [ "$INTERACTIVE" != "no" ]; then + echo "Not doing dput since it's a binary release. Do you want to install it? (Y/N)" + read install + if [ "$install" == "Y" ]; then + sudo dpkg -i $DIRECTORY*.deb + fi; + if [ "$DISTRO" == "jessie" ]; then + echo "Do you want to upload this binary to zmrepo? (y/N)" + read install + if [ "$install" == "Y" ]; then + scp "zoneminder_*-${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/" + fi; + fi; + fi; +else + SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + PPA=""; + if [ "$RELEASE" != "" ]; then + PPA="ppa:iconnor/zoneminder"; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + + dput="Y"; + if [ "$INTERACTIVE" != "no" ]; then + echo "Ready to dput $SC to $PPA ? Y/N..."; + read dput + fi + if [ "$dput" == "Y" -o "$dput" == "y" ]; then + dput $PPA $SC + fi; +fi; diff --git a/utils/docker/apache-vhost b/utils/docker/apache-vhost deleted file mode 100644 index 514897a16..000000000 --- a/utils/docker/apache-vhost +++ /dev/null @@ -1,12 +0,0 @@ - - DocumentRoot /usr/local/share/zoneminder - DirectoryIndex index.php - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - require all granted - - - diff --git a/utils/docker/setup.sh b/utils/docker/setup.sh index fbd3592f0..603035a0b 100755 --- a/utils/docker/setup.sh +++ b/utils/docker/setup.sh @@ -1,7 +1,10 @@ #!/bin/bash # Start MySQL -/usr/bin/mysqld_safe & +# For Xenial the following won't start mysqld +#/usr/bin/mysqld_safe & +# Use this instead: +service mysql start # Give MySQL time to wake up SECONDS_LEFT=120 @@ -27,6 +30,9 @@ mysql -u root < db/zm_create.sql # Add the ZoneMinder DB user mysql -u root -e "grant insert,select,update,delete,lock tables,alter on zm.* to 'zmuser'@'localhost' identified by 'zmpass';" +# Make ZM_LOGDIR +mkdir /var/log/zm + # Activate CGI a2enmod cgi diff --git a/utils/docker/start.sh b/utils/docker/start.sh index ff3d6c705..86247e734 100755 --- a/utils/docker/start.sh +++ b/utils/docker/start.sh @@ -7,7 +7,8 @@ umount /dev/shm mount -t tmpfs -o rw,nosuid,nodev,noexec,relatime,size=512M tmpfs /dev/shm # Start MySQL -/usr/bin/mysqld_safe & +test -e /var/run/mysqld || install -m 755 -o mysql -g root -d /var/run/mysqld +su - mysql -s /bin/sh -c "/usr/bin/mysqld_safe > /dev/null 2>&1 &" # Ensure we shut down mysql cleanly later: trap close_mysql SIGTERM @@ -36,9 +37,6 @@ service apache2 restart # Start ZoneMinder /usr/local/bin/zmpkg.pl start -# Start SSHD -/usr/sbin/sshd - while : do sleep 3600 diff --git a/utils/packpack/deploy_rsa b/utils/packpack/deploy_rsa new file mode 100644 index 000000000..4c4d552bb --- /dev/null +++ b/utils/packpack/deploy_rsa @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6tO+Kuj85uS4Z7ZmyAjmeAJbKbuv+pddPncgY/0/cSKfLbDo5f+TFM9K5AUlegcALbccUA9jt5p615KTgN0ZyGiCDcNs6/1DcDh4Nb/KgHsApVLZHZYpd7bnP5yadIqXroXw9VI2PWhUTasb97t4xvEepcsnepdfeGFegX7jyeRqucdEsZZa8kiSgU9hIdJyTeQXQQY5odYqABvR6ea7ff8iD2pdzjXIFHiA9527fXoPuUJo5rwIWOwtwstFG5xCT6ZPBgi2nECSZoRdG7zqkm2gqhAaNiR8PR5Qr0CbeYa4LWYl0v33CwLxFuyiP9AOqdFB+vF9c67do4E0yJ+heUNuOUbp3ePT1jJDOhQapAjkmyEb4A+RMNc9SEXmJh10nUiJ2zGGb9a3FyreopHfjdzKiZdk+uSV18kdG7KSiRvHep+sEK7xMepAQ3OiAprwOH2D2EoXC08TLehaeXmsOAuMGz0JvJKBXMGAow2SEa38v42PJwKsjn0qo72IjN1h/9ICgM/o+4oNHLHsu5GK1dornAt9OE6g1be1jXdij07QaSV831NiweOHqRTPpl2eglWfoyMjPuzebiyoAqb4tNBnMOk5BTaxx7ZZGXmdHyH9kKFAVj+WBkRHNlDhaMB2/QzhHUs0PRVRUexBKgog+1xnPE5gTie6NNnDB04NcDQ== zmrepo@zmrepo.zoneminder.com diff --git a/utils/packpack/deploy_rsa.enc b/utils/packpack/deploy_rsa.enc new file mode 100644 index 000000000..baaf90711 Binary files /dev/null and b/utils/packpack/deploy_rsa.enc differ diff --git a/utils/packpack/installzm.sh b/utils/packpack/installzm.sh new file mode 100755 index 000000000..31e54866f --- /dev/null +++ b/utils/packpack/installzm.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# No longer needed. This was incorporated into startpackpack.sh + +# Required, so that Travis marks the build as failed if any of the steps below fail +set -ev + +# Install and test the zoneminder package (only) for Ubuntu Trusty +if [ ${OS} == "ubuntu" ] && [ ${DIST} == "trusty" ]; then + sudo gdebi --non-interactive build/zoneminder_*amd64.deb + sudo chmod 644 /etc/zm/zm.conf + mysql -uzmuser -pzmpass zm < db/test.monitor.sql + sudo /usr/bin/zmpkg.pl start + sudo /usr/bin/zmfilter.pl -f purgewhenfull +fi + diff --git a/utils/packpack/packpack-rpm.patch b/utils/packpack/packpack-rpm.patch new file mode 100644 index 000000000..6d6b0e7f3 --- /dev/null +++ b/utils/packpack/packpack-rpm.patch @@ -0,0 +1,10 @@ +--- a/packpack/pack/rpm.mk 2017-05-10 09:53:38.797616947 -0500 ++++ b/packpack/pack/rpm.mk 2017-05-10 09:59:26.744409073 -0500 +@@ -26,7 +26,6 @@ + -e 's/Source0:\([ ]*\).*/Source0: $(TARBALL)/' \ + -e 's/%setup.*/%setup -q -n $(PRODUCT)-$(VERSION)/' \ + -re 's/(%autosetup.*)( -n \S*)(.*)/\1\3/' \ +- -e '0,/%autosetup.*/ s/%autosetup.*/%autosetup -n $(PRODUCT)-$(VERSION)/' \ + -e '/%changelog/a\* $(THEDATE) $(CHANGELOG_NAME) <$(CHANGELOG_EMAIL)> - $(VERSION)-$(RELEASE)\n\- $(CHANGELOG_TEXT)\n' \ + -i $@.tmp + grep -F "Version: $(VERSION)" $@.tmp && \ diff --git a/utils/packpack/redhat_package.mk b/utils/packpack/redhat_package.mk new file mode 100644 index 000000000..c15e03528 --- /dev/null +++ b/utils/packpack/redhat_package.mk @@ -0,0 +1,8 @@ +.PHONY: redhat_package +.NOTPARALLEL: redhat_package + +redhat_package: redhat_bootstrap package + +redhat_bootstrap: + sudo yum install -y --nogpgcheck build/zmrepo.noarch.rpm + diff --git a/utils/packpack/rsync_xfer.sh b/utils/packpack/rsync_xfer.sh new file mode 100755 index 000000000..e981f646f --- /dev/null +++ b/utils/packpack/rsync_xfer.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Check to see if this script has access to all the commands it needs +for CMD in sshfs rsync find fusermount mkdir; do + type $CMD 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"${CMD}\"." + echo + exit 1 + fi +done + +# We only want to deploy packages during cron events +# See https://docs.travis-ci.com/user/cron-jobs/ +if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then + + if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then + targetfolder="debian/master/mini-dinstall/incoming" + else + targetfolder="travis" + fi + + echo + echo "Target subfolder set to $targetfolder" + echo + + mkdir -p ./zmrepo + ssh_mntchk="$(sshfs zmrepo@zmrepo.zoneminder.com:./ ./zmrepo -o workaround=rename,reconnect)" + + if [ -z "$ssh_mntchk" ]; then + echo + echo "Remote filesystem mounted successfully." + echo "Begin transfering files..." + echo + + # Don't keep packages older than 5 days + find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete + rsync -vzh --ignore-errors build/* zmrepo/$targetfolder/ + fusermount -zu zmrepo + else + echo + echo "ERROR: Attempt to mount zmrepo.zoneminder.com failed!" + echo "sshfs gave the following error message:" + echo \"$ssh_mntchk\" + echo + exit 99 + fi +fi diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh new file mode 100755 index 000000000..a64be651a --- /dev/null +++ b/utils/packpack/startpackpack.sh @@ -0,0 +1,266 @@ +#!/bin/bash +# packpack setup file for the ZoneMinder project +# Written by Andrew Bauer + +############### +# SUBROUTINES # +############### + +# General sanity checks +checksanity () { + # Check to see if this script has access to all the commands it needs + for CMD in set echo curl repoquery git ln mkdir rmdir cat patch; do + type $CMD 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"${CMD}\"." + echo + exit 1 + fi + done + + # Verify OS & DIST environment variables have been set before calling this script + if [ -z "${OS}" ] || [ -z "${DIST}" ]; then + echo "ERROR: both OS and DIST environment variables must be set" + exit 1 + fi + + if [ -z "${ARCH}" ]; then + ARCH="x86_64" + fi + + if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then + echo + echo "ERROR: Unsupported architecture specified \"${ARCH}\"." + echo + exit 1 + fi + +} + +# Check key variables before calling packpack +checkvars () { + if [ -z ${VERSION} ]; then + echo + echo "FATAL: VERSION variable was null. Cannot continue." + echo + exit 98 + fi + + if [ -z ${RELEASE} ]; then + echo + echo "FATAL: RELEASE variable was null. Cannot Continue" + echo + exit 98 + fi +} + +# Steps common to all builds +commonprep () { + mkdir -p build + if [ -e "packpack/Makefile" ]; then + echo "Checking packpack github repo for changes..." + git -C packpack pull origin master + else + echo "Cloning packpack github repo..." + git clone https://github.com/packpack/packpack.git packpack + fi + + # Patch packpack + patch --dry-run --silent -f -p1 < utils/packpack/packpack-rpm.patch + if [ $? -eq 0 ]; then + patch -p1 < utils/packpack/packpack-rpm.patch + fi + + # The rpm specfile requires we download the tarball and manually move it into place + # Might as well do this for Debian as well, rather than git submodule init + CRUDVER="3.0.10" + if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then + echo "Found existing Crud ${CRUDVER} tarball..." + else + echo "Retrieving Crud ${CRUDVER} submodule..." + curl -L https://github.com/FriendsOfCake/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz + if [ $? -ne 0 ]; then + echo "ERROR: Crud tarball retreival failed..." + exit 1 + fi + fi +} + +# Uncompress the Crud tarball and move it into place +movecrud () { + if [ -e "web/api/app/Plugin/Crud/LICENSE.txt" ]; then + echo "Crud plugin already installed..." + else + echo "Unpacking Crud plugin..." + tar -xzf build/crud-${CRUDVER}.tar.gz + rmdir web/api/app/Plugin/Crud + mv -f crud-${CRUDVER} web/api/app/Plugin/Crud + fi +} + +# previsouly part of installzm.sh +# install the trusty deb and test zoneminder +installtrusty () { + + # Check we've got gdebi installed + type gdebi 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"gdebi\"." + echo + exit 1 + fi + + # Install and test the zoneminder package (only) for Ubuntu Trusty + pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" + + if [ -e $pkgname ]; then + sudo gdebi --non-interactive $pkgname + mysql -uzmuser -pzmpass zm < db/test.monitor.sql + sudo /usr/bin/zmpkg.pl start + sudo /usr/bin/zmfilter.pl -f purgewhenfull + else + echo + echo "ERROR: The script cannot find the package $pkgname" + echo "Check the Travis log for a build failure." + echo + exit 99 + fi +} + +# This sets the naming convention for the deb packages +setdebpkgver () { + + # Set VERSION to x.xx.x+x e.g. 1.30.2+15 + # the last x is number of commits since release + # Creates zoneminder packages in the format: zoneminder-{version}-{release} + zmver=$(git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\1/p') + commitnum=$(git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\2/p') + export VERSION="$zmver+$commitnum" + export RELEASE="${DIST}" + + checkvars + + echo + echo "Packpack VERSION has been set to: ${VERSION}" + echo "Packpack RELEASE has been set to: ${RELEASE}" + echo + +} + +# This adds an entry to the debian changelog +setdebchangelog () { +DATE=`date -R` +cat < debian/changelog +zoneminder ($VERSION-${DIST}-1) unstable; urgency=low + * + -- Isaac Connor $DATE +EOF +} + +################ +# MAIN PROGRAM # +################ + +checksanity + +# We don't want to build packages for all supported distros after every commit +# Only build all packages when executed via cron +# See https://docs.travis-ci.com/user/cron-jobs/ +if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then + commonprep + + # Steps common to Redhat distros + if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then + echo "Begin Redhat build..." + + # Set VERSION to x.xx.x e.g. 1.30.2 + # Set RELEASE to x where x is number of commits since release + # Creates zoneminder packages in the format: zoneminder-{version}-{release} + export VERSION=$(git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\1/p') + export RELEASE=$(git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\2/p') + + checkvars + + echo + echo "Packpack VERSION has been set to: ${VERSION}" + echo "Packpack RELEASE has been set to: ${RELEASE}" + echo + + ln -sfT distros/redhat rpm + + # The rpm specfile requires the Crud submodule folder to be empty + rm -rf web/api/app/Plugin/Crud + mkdir web/api/app/Plugin/Crud + + if [ "${OS}" == "el" ]; then + zmrepodistro=${OS} + else + zmrepodistro="f" + fi + + # Let repoquery determine the full url and filename of the zmrepo rpm we are interested in + result=`repoquery --repofrompath=zmpackpack,https://zmrepo.zoneminder.com/${zmrepodistro}/"${DIST}"/x86_64/ --repoid=zmpackpack --qf="%{location}" zmrepo 2> /dev/null` + + if [ -n "$result" ] && [ $? -eq 0 ]; then + echo "Retrieving ZMREPO rpm..." + curl $result > build/zmrepo.noarch.rpm + else + echo "ERROR: Failed to retrieve zmrepo rpm..." + exit 1 + fi + + echo "Starting packpack..." + packpack/packpack -f utils/packpack/redhat_package.mk redhat_package + + # Steps common to Debian based distros + elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then + echo "Begin ${OS} ${DIST} build..." + + setdebpkgver + movecrud + + if [ "${DIST}" == "trusty" ] || [ "${DIST}" == "precise" ]; then + ln -sfT distros/ubuntu1204 debian + elif [ "${DIST}" == "wheezy" ]; then + ln -sfT distros/debian debian + else + ln -sfT distros/ubuntu1604 debian + fi + + setdebchangelog + + echo "Starting packpack..." + packpack/packpack + + if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then + installtrusty + fi + fi + +# We were not triggered via cron so just build and test trusty +elif [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ]; then + echo "Begin Ubuntu Trusty build..." + + commonprep + setdebpkgver + movecrud + + ln -sfT distros/ubuntu1204 debian + + setdebchangelog + + echo "Starting packpack..." + packpack/packpack + + # If we are running inside Travis then attempt to install the deb we just built + if [ "${TRAVIS}" == "true" ]; then + installtrusty + fi +fi + +exit 0 + diff --git a/version b/version index 7f3c3affd..6bae54024 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.30.1 +1.31.1 diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 000000000..217a0e30c --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,13 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +indent_style = tab +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.bat] +end_of_line = crlf diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 000000000..90d971d4b --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,22 @@ +# User specific & automatically generated files # +################################################# +/app/Config/database.php +/app/tmp +/lib/Cake/Console/Templates/skel/tmp/ +/plugins +/vendors +/build +/dist +/tags +/app/webroot/events + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 000000000..f23dbaf66 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,5 @@ + + RewriteEngine on + RewriteRule ^$ app/webroot/ [L] + RewriteRule (.*) app/webroot/$1 [L] + \ No newline at end of file diff --git a/web/.travis.yml b/web/.travis.yml new file mode 100644 index 000000000..5283442c4 --- /dev/null +++ b/web/.travis.yml @@ -0,0 +1,116 @@ +language: php + +php: + - 5.2 + - 5.3 + - 5.4 + +env: + - DB=mysql + - DB=pgsql + - DB=sqlite + +matrix: + include: + - php: 5.4 + env: + - PHPCS=1 + +before_script: + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test2;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test3;'; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test2;' -U postgres -d cakephp_test; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi" + - chmod -R 777 ./app/tmp + - sudo apt-get install lighttpd + - pear channel-discover pear.cakephp.org + - pear install --alldeps cakephp/CakePHP_CodeSniffer + - phpenv rehash + - set +H + - echo " array( + 'datasource' => 'Database/Mysql', + 'host' => '0.0.0.0', + 'login' => 'travis' + ), + 'pgsql' => array( + 'datasource' => 'Database/Postgres', + 'host' => '127.0.0.1', + 'login' => 'postgres', + 'database' => 'cakephp_test', + 'schema' => array( + 'default' => 'public', + 'test' => 'public', + 'test2' => 'test2', + 'test_database_three' => 'test3' + ) + ), + 'sqlite' => array( + 'datasource' => 'Database/Sqlite', + 'database' => array( + 'default' => ':memory:', + 'test' => ':memory:', + 'test2' => '/tmp/cakephp_test2.db', + 'test_database_three' => '/tmp/cakephp_test3.db' + ), + ) + ); + public \$default = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test2 = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test2', + 'prefix' => '' + ); + public \$test_database_three = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test3', + 'prefix' => '' + ); + public function __construct() { + \$db = 'mysql'; + if (!empty(\$_SERVER['DB'])) { + \$db = \$_SERVER['DB']; + } + foreach (array('default', 'test', 'test2', 'test_database_three') as \$source) { + \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]); + if (is_array(\$config['database'])) { + \$config['database'] = \$config['database'][\$source]; + } + if (!empty(\$config['schema']) && is_array(\$config['schema'])) { + \$config['schema'] = \$config['schema'][\$source]; + } + \$this->{\$source} = \$config; + } + } + }" > app/Config/database.php + +script: + - sh -c "if [ '$PHPCS' != '1' ]; then ./lib/Cake/Console/cake test core AllTests --stderr; else phpcs -p --extensions=php --standard=CakePHP ./lib/Cake; fi" + +notifications: + email: false \ No newline at end of file diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 201b5b511..e9b156068 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -18,6 +18,7 @@ if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) # Install the mootools symlinks (if its not in the source directory) diff --git a/web/ajax/event.php b/web/ajax/event.php index a78ccd1a9..5291f0f7a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -6,7 +6,7 @@ if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) { if ( canView( 'Events' ) ) { switch ( $_REQUEST['action'] ) { - case "video" : { + case 'video' : { if ( empty($_REQUEST['videoFormat']) ) { ajaxError( "Video Generation Failure, no format given" ); } elseif ( empty($_REQUEST['rate']) ) { @@ -77,11 +77,9 @@ if ( canView( 'Events' ) ) { } } -if ( canEdit( 'Events' ) ) -{ - switch ( $_REQUEST['action'] ) - { - case "rename" : +if ( canEdit( 'Events' ) ) { + switch ( $_REQUEST['action'] ) { + case 'rename' : { if ( !empty($_REQUEST['eventName']) ) dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); @@ -90,24 +88,29 @@ if ( canEdit( 'Events' ) ) ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); break; } - case "eventdetail" : + case 'eventdetail' : { dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); break; } - case "archive" : - case "unarchive" : + case 'archive' : + case 'unarchive' : { $archiveVal = ($_REQUEST['action'] == "archive")?1:0; dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); break; } - case "delete" : + case 'delete' : { - deleteEvent( $_REQUEST['id'] ); - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + $Event = new Event( $_REQUEST['id'] ); + if ( ! $Event->Id() ) { + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.' ) ); + } else { + $Event->delete(); + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + } break; } } diff --git a/web/ajax/log.php b/web/ajax/log.php index 66dcfc2b5..c38a6d5d0 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,5 +1,9 @@ Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); +# There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; $maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; - $limit = isset($_POST['limit'])?$_POST['limit']:100; - $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; + $limit = 100; + if ( isset($_POST['limit']) ) { + if ( ( !is_integer( $_POST['limit'] ) and !ctype_digit($_POST['limit']) ) ) { + Error("Invalid value for limit " . $_POST['limit'] ); + } else { + $limit = $_POST['limit']; + } + } + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; + $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $filterFields = array( 'Component', 'ServerId', 'Pid', 'Level', 'File', 'Line' ); - - $total = dbFetchOne( "SELECT count(*) AS Total FROM Logs", 'Total' ); + $total = dbFetchOne( 'SELECT count(*) AS Total FROM Logs', 'Total' ); $sql = 'SELECT * FROM Logs'; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { - $where[] = "TimeKey > ?"; - $values[] = $minTime; + $where[] = "TimeKey > ?"; + $values[] = $minTime; } elseif ( $maxTime ) { - $where[] = "TimeKey < ?"; - $values[] = $maxTime; - } + $where[] = "TimeKey < ?"; + $values[] = $maxTime; + } + foreach ( $filter as $field=>$value ) { - if ( $field == 'Level' ){ - $where[] = $field." <= ?"; - $values[] = $value; - } else { - $where[] = $field." = ?"; - $values[] = $value; - } - } + if ( ! in_array( $field, $filterFields ) ) { + Error("$field is not in valid filter fields"); + continue; + } + if ( $field == 'Level' ){ + $where[] = $field." <= ?"; + $values[] = $value; + } else { + $where[] = $field." = ?"; + $values[] = $value; + } + } if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); + $sql.= ' WHERE '.join( ' AND ', $where ); $sql .= " order by ".$sortField." ".$sortOrder." limit ".$limit; $logs = array(); foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } $options = array(); $where = array(); - $values = array(); + $values = array(); foreach( $filter as $field=>$value ) { if ( $field == 'Level' ) { $where[$field] = $field." <= ?"; - $values[$field] = $value; + $values[$field] = $value; } else { $where[$field] = $field." = ?"; - $values[$field] = $value; - } - } + $values[$field] = $value; + } + } foreach( $filterFields as $field ) { $sql = "SELECT DISTINCT $field FROM Logs WHERE NOT isnull($field)"; $fieldWhere = array_diff_key( $where, array( $field=>true ) ); - $fieldValues = array_diff_key( $values, array( $field=>true ) ); + $fieldValues = array_diff_key( $values, array( $field=>true ) ); if ( count($fieldWhere) ) $sql.= " AND ".join( ' AND ', $fieldWhere ); $sql.= " ORDER BY $field ASC"; @@ -108,7 +129,7 @@ switch ( $_REQUEST['task'] ) { foreach( dbFetchAll( $sql, $field, array_values($fieldValues) ) as $value ) $options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : ''; - + } else { @@ -147,44 +168,51 @@ switch ( $_REQUEST['task'] ) } //$limit = isset($_POST['limit'])?$_POST['limit']:1000; $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; - $sortOrder = isset($_POST['sortOrder'])?$_POST['sortOrder']:'asc'; + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } + $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; - $servers = Server::find_all(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); + # There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $sql = "select * from Logs"; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { preg_match( '/(.+)(\.\d+)/', $minTime, $matches ); $minTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey >= ?"; - $values[] = $minTime; + $values[] = $minTime; } if ( $maxTime ) { preg_match( '/(.+)(\.\d+)/', $maxTime, $matches ); $maxTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey <= ?"; - $values[] = $maxTime; + $values[] = $maxTime; } foreach ( $filter as $field=>$value ) { if ( $value != '' ) { if ( $field == 'Level' ) { $where[] = $field." <= ?"; - $values[] = $value; + $values[] = $value; } else { $where[] = $field." = ?'"; - $values[] = $value; - } - } - } + $values[] = $value; + } + } + } if ( count($where) ) $sql.= " where ".join( " and ", $where ); $sql .= " order by ".$sortField." ".$sortOrder; @@ -216,7 +244,7 @@ switch ( $_REQUEST['task'] ) foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } switch( $format ) @@ -234,20 +262,20 @@ switch ( $_REQUEST['task'] ) } case 'tsv' : { - # This line doesn't need fprintf, it could use fwrite +# This line doesn't need fprintf, it could use fwrite fprintf( $exportFP, join( "\t", - translate('DateTime'), - translate('Component'), - translate('Server'), - translate('Pid'), - translate('Level'), - translate('Message'), - translate('File'), - translate('Line') - )."\n" ); + translate('DateTime'), + translate('Component'), + translate('Server'), + translate('Pid'), + translate('Level'), + translate('Message'), + translate('File'), + translate('Line') + )."\n" ); foreach ( $logs as $log ) { - fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); + fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); } break; } diff --git a/web/ajax/status.php b/web/ajax/status.php index f7ed3d099..fbd91cf08 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -2,415 +2,395 @@ $statusData = array( "system" => array( - "permission" => "System", - "table" => "Monitors", - "limit" => 1, - "elements" => array( - "MonitorCount" => array( "sql" => "count(*)" ), - "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), - "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), - "Load" => array( "func" => "getLoad()" ), - "Disk" => array( "func" => "getDiskPercent()" ), + "permission" => "System", + "table" => "Monitors", + "limit" => 1, + "elements" => array( + "MonitorCount" => array( "sql" => "count(*)" ), + "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), + "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), + "Load" => array( "func" => "getLoad()" ), + "Disk" => array( "func" => "getDiskPercent()" ), ), - ), + ), "monitor" => array( - "permission" => "Monitors", - "table" => "Monitors", - "limit" => 1, - "selector" => "Monitors.Id", - "elements" => array( - "Id" => array( "sql" => "Monitors.Id" ), - "Name" => array( "sql" => "Monitors.Name" ), - "Type" => true, - "Function" => true, - "Enabled" => true, - "LinkedMonitors" => true, - "Triggers" => true, - "Device" => true, - "Channel" => true, - "Format" => true, - "Host" => true, - "Port" => true, - "Path" => true, - "Width" => array( "sql" => "Monitors.Width" ), - "Height" => array( "sql" => "Monitors.Height" ), - "Palette" => true, - "Orientation" => true, - "Brightness" => true, - "Contrast" => true, - "Hue" => true, - "Colour" => true, - "EventPrefix" => true, - "LabelFormat" => true, - "LabelX" => true, - "LabelY" => true, - "LabelSize" => true, - "ImageBufferCount" => true, - "WarmupCount" => true, - "PreEventCount" => true, - "PostEventCount" => true, - "AlarmFrameCount" => true, - "SectionLength" => true, - "FrameSkip" => true, - "MotionFrameSkip" => true, - "MaxFPS" => true, - "AlarmMaxFPS" => true, - "FPSReportInterval" => true, - "RefBlendPerc" => true, - "Controllable" => true, - "ControlId" => true, - "ControlDevice" => true, - "ControlAddress" => true, - "AutoStopTimeout" => true, - "TrackMotion" => true, - "TrackDelay" => true, - "ReturnLocation" => true, - "ReturnDelay" => true, - "DefaultView" => true, - "DefaultRate" => true, - "DefaultScale" => true, - "WebColour" => true, - "Sequence" => true, - "MinEventId" => array( "sql" => "min(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "MaxEventId" => array( "sql" => "max(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "TotalEvents" => array( "sql" => "count(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), - "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), + "permission" => "Monitors", + "table" => "Monitors", + "limit" => 1, + "selector" => "Monitors.Id", + "elements" => array( + "Id" => array( "sql" => "Monitors.Id" ), + "Name" => array( "sql" => "Monitors.Name" ), + "Type" => true, + "Function" => true, + "Enabled" => true, + "LinkedMonitors" => true, + "Triggers" => true, + "Device" => true, + "Channel" => true, + "Format" => true, + "Host" => true, + "Port" => true, + "Path" => true, + "Width" => array( "sql" => "Monitors.Width" ), + "Height" => array( "sql" => "Monitors.Height" ), + "Palette" => true, + "Orientation" => true, + "Brightness" => true, + "Contrast" => true, + "Hue" => true, + "Colour" => true, + "EventPrefix" => true, + "LabelFormat" => true, + "LabelX" => true, + "LabelY" => true, + "LabelSize" => true, + "ImageBufferCount" => true, + "WarmupCount" => true, + "PreEventCount" => true, + "PostEventCount" => true, + "AlarmFrameCount" => true, + "SectionLength" => true, + "FrameSkip" => true, + "MotionFrameSkip" => true, + "MaxFPS" => true, + "AlarmMaxFPS" => true, + "FPSReportInterval" => true, + "RefBlendPerc" => true, + "Controllable" => true, + "ControlId" => true, + "ControlDevice" => true, + "ControlAddress" => true, + "AutoStopTimeout" => true, + "TrackMotion" => true, + "TrackDelay" => true, + "ReturnLocation" => true, + "ReturnDelay" => true, + "DefaultView" => true, + "DefaultRate" => true, + "DefaultScale" => true, + "WebColour" => true, + "Sequence" => true, + "MinEventId" => array( "sql" => "(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "MaxEventId" => array( "sql" => "(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "TotalEvents" => array( "sql" => "(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), + "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), ), - ), - "events" => array( - "permission" => "Events", - "table" => "Events", - "selector" => "Events.MonitorId", - "elements" => array( - "Id" => true, - "Name" => true, - "Cause" => true, - "Notes" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - ), - ), - "event" => array( - "permission" => "Events", - "table" => "Events", - "limit" => 1, - "selector" => "Events.Id", - "elements" => array( - "Id" => array( "sql" => "Events.Id" ), - "MonitorId" => true, - "Name" => true, - "Cause" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - "Archived" => true, - "Videoed" => true, - "Uploaded" => true, - "Emailed" => true, - "Messaged" => true, - "Executed" => true, - "Notes" => true, - "MinFrameId" => array( "sql" => "min(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameId" => array( "sql" => "max(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MinFrameDelta" => array( "sql" => "min(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameDelta" => array( "sql" => "max(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - //"Path" => array( "postFunc" => "getEventPath" ), - ), - ), - "frame" => array( - "permission" => "Events", - "table" => "Frames", - "limit" => 1, - "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), - "elements" => array( - //"Id" => array( "sql" => "Frames.FrameId" ), - "FrameId" => true, - "EventId" => true, - "Type" => true, - "TimeStamp" => true, - "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "Delta" => true, - "Score" => true, - //"Image" => array( "postFunc" => "getFrameImage" ), - ), - ), - "frameimage" => array( - "permission" => "Events", - "func" => "getFrameImage()" - ), - "nearframe" => array( - "permission" => "Events", - "func" => "getNearFrame()" - ), - "nearevents" => array( - "permission" => "Events", - "func" => "getNearEvents()" - ) -); + ), + "events" => array( + "permission" => "Events", + "table" => "Events", + "selector" => "Events.MonitorId", + "elements" => array( + "Id" => true, + "Name" => true, + "Cause" => true, + "Notes" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + ), + ), + "event" => array( + "permission" => "Events", + "table" => "Events", + "limit" => 1, + "selector" => "Events.Id", + "elements" => array( + "Id" => array( "sql" => "Events.Id" ), + "MonitorId" => true, + "Name" => true, + "Cause" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "DefaultVideo" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + "Archived" => true, + "Videoed" => true, + "Uploaded" => true, + "Emailed" => true, + "Messaged" => true, + "Executed" => true, + "Notes" => true, + "MinFrameId" => array( "sql" => "(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)" ), + "MaxFrameId" => array( "sql" => "(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MinFrameDelta" => array( "sql" => "(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MaxFrameDelta" => array( "sql" => "(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + //"Path" => array( "postFunc" => "getEventPath" ), + ), + ), + "frame" => array( + "permission" => "Events", + "table" => "Frames", + "limit" => 1, + "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), + "elements" => array( + //"Id" => array( "sql" => "Frames.FrameId" ), + "FrameId" => true, + "EventId" => true, + "Type" => true, + "TimeStamp" => true, + "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "Delta" => true, + "Score" => true, + //"Image" => array( "postFunc" => "getFrameImage" ), + ), + ), + "frameimage" => array( + "permission" => "Events", + "func" => "getFrameImage()" + ), + "nearframe" => array( + "permission" => "Events", + "func" => "getNearFrame()" + ), + "nearevents" => array( + "permission" => "Events", + "func" => "getNearEvents()" + ) + ); -function collectData() -{ - global $statusData; +function collectData() { + global $statusData; - $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; - #print_r( $entitySpec ); - if ( !canView( $entitySpec['permission'] ) ) - ajaxError( 'Unrecognised action or insufficient permissions' ); + $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; +#print_r( $entitySpec ); + if ( !canView( $entitySpec['permission'] ) ) + ajaxError( 'Unrecognised action or insufficient permissions' ); - if ( !empty($entitySpec['func']) ) - { - $data = eval( "return( ".$entitySpec['func']." );" ); + if ( !empty($entitySpec['func']) ) { + $data = eval( "return( ".$entitySpec['func']." );" ); + } else { + $data = array(); + $postFuncs = array(); + + $fieldSql = array(); + $joinSql = array(); + $groupSql = array(); + + $elements = &$entitySpec['elements']; + $lc_elements = array_change_key_case( $elements ); + + $id = false; + if ( isset($_REQUEST['id']) ) + if ( !is_array($_REQUEST['id']) ) + $id = array( validJsStr($_REQUEST['id']) ); + else + $id = array_values( $_REQUEST['id'] ); + + if ( !isset($_REQUEST['element']) ) + $_REQUEST['element'] = array_keys( $elements ); + else if ( !is_array($_REQUEST['element']) ) + $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); + + if ( isset($entitySpec['selector']) ) { + if ( !is_array($entitySpec['selector']) ) + $entitySpec['selector'] = array( $entitySpec['selector'] ); + foreach( $entitySpec['selector'] as $selector ) + if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) + $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; } - else - { - $data = array(); - $postFuncs = array(); - $fieldSql = array(); - $joinSql = array(); - $groupSql = array(); - - $elements = &$entitySpec['elements']; - $lc_elements = array_change_key_case( $elements ); - - $id = false; - if ( isset($_REQUEST['id']) ) - if ( !is_array($_REQUEST['id']) ) - $id = array( validJsStr($_REQUEST['id']) ); - else - $id = array_values( $_REQUEST['id'] ); - - if ( !isset($_REQUEST['element']) ) - $_REQUEST['element'] = array_keys( $elements ); - else if ( !is_array($_REQUEST['element']) ) - $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); - - if ( isset($entitySpec['selector']) ) - { - if ( !is_array($entitySpec['selector']) ) - $entitySpec['selector'] = array( $entitySpec['selector'] ); - foreach( $entitySpec['selector'] as $selector ) - if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) - $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; + foreach ( $_REQUEST['element'] as $element ) { + if ( !($elementData = $lc_elements[strtolower($element)]) ) + ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); + if ( isset($elementData['func']) ) + $data[$element] = eval( "return( ".$elementData['func']." );" ); + else if ( isset($elementData['postFunc']) ) + $postFuncs[$element] = $elementData['postFunc']; + else if ( isset($elementData['zmu']) ) + $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); + else { + if ( isset($elementData['sql']) ) + $fieldSql[] = $elementData['sql']." as ".$element; + else + $fieldSql[] = $element; + if ( isset($elementData['table']) && isset($elementData['join']) ) { + $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; } - - foreach ( $_REQUEST['element'] as $element ) - { - if ( !($elementData = $lc_elements[strtolower($element)]) ) - ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); - if ( isset($elementData['func']) ) - $data[$element] = eval( "return( ".$elementData['func']." );" ); - else if ( isset($elementData['postFunc']) ) - $postFuncs[$element] = $elementData['postFunc']; - else if ( isset($elementData['zmu']) ) - $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); - else - { - if ( isset($elementData['sql']) ) - $fieldSql[] = $elementData['sql']." as ".$element; - else - $fieldSql[] = $element; - if ( isset($elementData['table']) && isset($elementData['join']) ) - { - $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; - } - if ( isset($elementData['group']) ) - { - $groupSql[] = $elementData['group']; - } - } - } - - if ( count($fieldSql) ) - { - $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; - if ( $joinSql ) - $sql .= " ".join( " ", array_unique( $joinSql ) ); - if ( $id && !empty($entitySpec['selector']) ) - { - $index = 0; - $where = array(); - $values = array(); - foreach( $entitySpec['selector'] as $selector ) { - if ( is_array( $selector ) ) { - $where[] = $selector['selector'].' = ?'; - $values[] = validInt($id[$index]); - } else { - $where[] = $selector.' = ?'; - $values[] = validInt($id[$index]); - } - $index++; - } - $sql .= " where ".join( " and ", $where ); - } - if ( $groupSql ) - $sql .= " group by ".join( ",", array_unique( $groupSql ) ); - if ( !empty($_REQUEST['sort']) ) - $sql .= " order by ".$_REQUEST['sort']; - if ( !empty($entitySpec['limit']) ) - $limit = $entitySpec['limit']; - elseif ( !empty($_REQUEST['count']) ) - $limit = validInt($_REQUEST['count']); - $limit_offset=""; - if ( !empty($_REQUEST['offset']) ) - $limit_offset = validInt($_REQUEST['offset']) . ", "; - if ( !empty( $limit ) ) - $sql .= " limit ".$limit_offset.$limit; - if ( isset($limit) && $limit == 1 ) { - if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data = array_merge( $data, $sqlData ); - } - } else { - $count = 0; - foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data[] = $sqlData; - if ( isset($limi) && ++$count >= $limit ) - break; - } - } + if ( isset($elementData['group']) ) { + $groupSql[] = $elementData['group']; } + } } - #print_r( $data ); - return( $data ); + + if ( count($fieldSql) ) { + $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; + if ( $joinSql ) + $sql .= " ".join( " ", array_unique( $joinSql ) ); + if ( $id && !empty($entitySpec['selector']) ) { + $index = 0; + $where = array(); + $values = array(); + foreach( $entitySpec['selector'] as $selector ) { + if ( is_array( $selector ) ) { + $where[] = $selector['selector'].' = ?'; + $values[] = validInt($id[$index]); + } else { + $where[] = $selector.' = ?'; + $values[] = validInt($id[$index]); + } + $index++; + } + $sql .= " where ".join( " and ", $where ); + } + if ( $groupSql ) + $sql .= " GROUP BY ".join( ",", array_unique( $groupSql ) ); + if ( !empty($_REQUEST['sort']) ) + $sql .= " order by ".$_REQUEST['sort']; + if ( !empty($entitySpec['limit']) ) + $limit = $entitySpec['limit']; + elseif ( !empty($_REQUEST['count']) ) + $limit = validInt($_REQUEST['count']); + $limit_offset=""; + if ( !empty($_REQUEST['offset']) ) + $limit_offset = validInt($_REQUEST['offset']) . ", "; + if ( !empty( $limit ) ) + $sql .= " limit ".$limit_offset.$limit; + if ( isset($limit) && $limit == 1 ) { + if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data = array_merge( $data, $sqlData ); + } + } else { + $count = 0; + foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data[] = $sqlData; + if ( isset($limi) && ++$count >= $limit ) + break; + } + } + } + } +#print_r( $data ); + return( $data ); } $data = collectData(); -if ( !isset($_REQUEST['layout']) ) -{ - $_REQUEST['layout'] = "json"; +if ( !isset($_REQUEST['layout']) ) { + $_REQUEST['layout'] = "json"; } -switch( $_REQUEST['layout'] ) -{ - case 'xml NOT CURRENTLY SUPPORTED' : + +switch( $_REQUEST['layout'] ) { + case 'xml NOT CURRENTLY SUPPORTED' : { - header("Content-type: application/xml" ); - echo( ''."\n" ); - echo "<".strtolower($_REQUEST['entity']).">\n"; - foreach ( $data as $key=>$value ) - { - $key = strtolower( $key ); - echo "<$key>".htmlentities($value)."\n"; - } - echo "\n"; - break; + header("Content-type: application/xml" ); + echo( ''."\n" ); + echo "<".strtolower($_REQUEST['entity']).">\n"; + foreach ( $data as $key=>$value ) { + $key = strtolower( $key ); + echo "<$key>".htmlentities($value)."\n"; + } + echo "\n"; + break; } - case 'json' : + case 'json' : { - $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); - if ( isset($_REQUEST['loopback']) ) - $response['loopback'] = validJsStr($_REQUEST['loopback']); - ajaxResponse( $response ); - break; + $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); + if ( isset($_REQUEST['loopback']) ) + $response['loopback'] = validJsStr($_REQUEST['loopback']); + ajaxResponse( $response ); + break; } - case 'text' : + case 'text' : { - header("Content-type: text/plain" ); - echo join( " ", array_values( $data ) ); - break; + header("Content-type: text/plain" ); + echo join( " ", array_values( $data ) ); + break; } } -function getFrameImage() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getFrameImage() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select * from Frames where EventId = ? and FrameId = ?'; - if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) - { - $frame = array(); - $frame['EventId'] = $eventId; - $frame['FrameId'] = $frameId; - $frame['Type'] = "Virtual"; - } - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); - $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); - return( $frame ); + $sql = 'select * from Frames where EventId = ? and FrameId = ?'; + if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) { + $frame = array(); + $frame['EventId'] = $eventId; + $frame['FrameId'] = $frameId; + $frame['Type'] = "Virtual"; + } + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); + $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); + return( $frame ); } -function getNearFrame() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getNearFrame() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - return( array() ); - } + $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + return( array() ); } - $_REQUEST['entity'] = "frame"; - $_REQUEST['id'][1] = $nearFrameId; - return( collectData() ); + } + $_REQUEST['entity'] = "frame"; + $_REQUEST['id'][1] = $nearFrameId; + return( collectData() ); } -function getNearEvents() -{ - global $user, $sortColumn, $sortOrder; +function getNearEvents() { + global $user, $sortColumn, $sortOrder; - $eventId = $_REQUEST['id']; - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); + $eventId = $_REQUEST['id']; + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); - parseFilter( $_REQUEST['filter'] ); - parseSort(); + parseFilter( $_REQUEST['filter'] ); + parseSort(); - if ( $user['MonitorIds'] ) - $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; - else - $midSql = ''; + if ( $user['MonitorIds'] ) + $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; + else + $midSql = ''; - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $prevId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $prevEvent = dbFetchNext( $result ); + break; } + } - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $nextId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $nextEvent = dbFetchNext( $result ); + break; } + } - $result = array( 'EventId'=>$eventId ); - $result['PrevEventId'] = empty($prevId)?0:$prevId; - $result['NextEventId'] = empty($nextId)?0:$nextId; - return( $result ); + $result = array( 'EventId'=>$eventId ); + $result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id']; + $result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id']; + $result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent)); + $result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent)); + return( $result ); } ?> diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 4f453d50f..4b39c0ac0 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -21,23 +21,23 @@ if ( !@socket_bind( $socket, $locSockFile ) ) switch ( $_REQUEST['command'] ) { case CMD_VARPLAY : - Debug( "Varplaying to ".$_REQUEST['rate'] ); + Logger::Debug( "Varplaying to ".$_REQUEST['rate'] ); $msg = pack( "lcn", MSG_CMD, $_REQUEST['command'], $_REQUEST['rate']+32768 ); break; case CMD_ZOOMIN : - Debug( "Zooming to ".$_REQUEST['x'].",".$_REQUEST['y'] ); + Logger::Debug( "Zooming to ".$_REQUEST['x'].",".$_REQUEST['y'] ); $msg = pack( "lcnn", MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); break; case CMD_PAN : - Debug( "Panning to ".$_REQUEST['x'].",".$_REQUEST['y'] ); + Logger::Debug( "Panning to ".$_REQUEST['x'].",".$_REQUEST['y'] ); $msg = pack( "lcnn", MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); break; case CMD_SCALE : - Debug( "Scaling to ".$_REQUEST['scale'] ); + Logger::Debug( "Scaling to ".$_REQUEST['scale'] ); $msg = pack( "lcn", MSG_CMD, $_REQUEST['command'], $_REQUEST['scale'] ); break; case CMD_SEEK : - Debug( "Seeking to ".$_REQUEST['offset'] ); + Logger::Debug( "Seeking to ".$_REQUEST['offset'] ); $msg = pack( "lcN", MSG_CMD, $_REQUEST['command'], $_REQUEST['offset'] ); break; default : @@ -52,7 +52,7 @@ while ( !file_exists($remSockFile) && $max_socket_tries-- ) { //sometimes we are } if ( !file_exists($remSockFile) ) { - ajaxError("Socket $remSocketFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); + ajaxError("Socket $remSockFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); } else { if ( !@socket_sendto( $socket, $msg, strlen($msg), 0, $remSockFile ) ) { ajaxError( "socket_sendto( $remSockFile ) failed: ".socket_strerror(socket_last_error()) ); diff --git a/web/api/CMakeLists.txt b/web/api/CMakeLists.txt index 3d865527b..d2a39ef13 100644 --- a/web/api/CMakeLists.txt +++ b/web/api/CMakeLists.txt @@ -11,5 +11,8 @@ configure_file(app/Config/database.php.default "${CMAKE_CURRENT_BINARY_DIR}/app/ # Configure core.php configure_file(app/Config/core.php.default "${CMAKE_CURRENT_BINARY_DIR}/app/Config/core.php" @ONLY) -# Configure bootstrap.php +# Configure app/Config/bootstrap.php configure_file(app/Config/bootstrap.php.in "${CMAKE_CURRENT_BINARY_DIR}/app/Config/bootstrap.php" @ONLY) + +# Configure lib/Cake/bootstrap.php +configure_file(lib/Cake/bootstrap.php.in "${CMAKE_CURRENT_BINARY_DIR}/lib/Cake/bootstrap.php" @ONLY) diff --git a/web/api/app/Config/bootstrap.php.in b/web/api/app/Config/bootstrap.php.in index 7210fd35c..a2bfe9c26 100644 --- a/web/api/app/Config/bootstrap.php.in +++ b/web/api/app/Config/bootstrap.php.in @@ -23,7 +23,7 @@ */ // Setup a 'default' cache configuration for use in the application. -Cache::config('default', array('engine' => 'File')); +Cache::config('default', array('engine' => 'Apc')); /** * The settings below can be used to set additional paths to models, views and controllers. @@ -100,12 +100,16 @@ App::uses('CakeLog', 'Log'); CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), - 'file' => 'debug', + 'file' => '@ZM_LOGDIR@/cake_debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), - 'file' => 'error', + 'file' => '@ZM_LOGDIR@/cake_error', +)); +CakeLog::config('custom_path', array( + 'engine' => 'File', + 'path' => '@ZM_LOGDIR@' )); Configure::write('ZM_CONFIG', '@ZM_CONFIG@'); diff --git a/web/api/app/Config/core.php.default b/web/api/app/Config/core.php.default index 43736a61f..cffb43c77 100644 --- a/web/api/app/Config/core.php.default +++ b/web/api/app/Config/core.php.default @@ -31,7 +31,7 @@ * In production mode, flash messages redirect after a time interval. * In development mode, you need to click the flash message to continue. */ - Configure::write('debug', 2); + Configure::write('debug', 0); /** * Configure the Error handler used to handle errors for your application. By default @@ -352,7 +352,7 @@ * Please check the comments in bootstrap.php for more info on the cache engines available * and their settings. */ -$engine = 'File'; +$engine = 'Apc'; // In development mode, caches should expire quickly. $duration = '+999 days'; diff --git a/web/api/app/Config/database.php.default b/web/api/app/Config/database.php.default index aeaba1cb1..55f2bc958 100644 --- a/web/api/app/Config/database.php.default +++ b/web/api/app/Config/database.php.default @@ -88,7 +88,7 @@ class DATABASE_CONFIG { public function __construct() { if (strpos(ZM_DB_HOST, ':')): $array = explode(':', ZM_DB_HOST, 2); - if (is_numeric($array[1])): + if (ctype_digit($array[1])): $this->default['host'] = $array[0]; $this->default['port'] = $array[1]; else: diff --git a/web/api/app/Controller/AppController.php b/web/api/app/Controller/AppController.php index 6775fa846..e9c84ab4f 100644 --- a/web/api/app/Controller/AppController.php +++ b/web/api/app/Controller/AppController.php @@ -88,10 +88,35 @@ class AppController extends Controller { } } + if ( isset($_REQUEST['auth']) ) { + require_once "../../../includes/functions.php"; + + // Define some defines required by getAuthUser in functions.php + $defines = array('ZM_AUTH_HASH_IPS', 'ZM_AUTH_HASH_SECRET', 'ZM_AUTH_RELAY', 'ZM_OPT_USE_AUTH'); + $configQuery = array( + 'conditions' => array('OR' => array('Name' => $defines)), + 'fields' => array('Name', 'Value') + ); + $config = $this->Config->find('list', $configQuery); + + foreach ($defines as $define) { + define($define, $config[$define]); + } + + $user = getAuthUser($_REQUEST['auth']); + if ( ! $user ) { + throw new UnauthorizedException(__('User not found')); + return; + } else { + $this->Session->Write( 'user.Username', $user['Username'] ); + $this->Session->Write( 'user.Enabled', $user['Enabled'] ); + } + } + if( ! $this->Session->Read('user.Username') ) { throw new UnauthorizedException(__('Not Authenticated')); return; - } else if ( ! $this->Session->Read('user.Username') ) { + } else if ( ! $this->Session->Read('user.Enabled') ) { throw new UnauthorizedException(__('User is not enabled')); return; } diff --git a/web/api/app/Controller/Component/ImageComponent.php b/web/api/app/Controller/Component/ImageComponent.php index f8ed7a533..fc967a2de 100644 --- a/web/api/app/Controller/Component/ImageComponent.php +++ b/web/api/app/Controller/Component/ImageComponent.php @@ -94,7 +94,10 @@ class ImageComponent extends Component { // Take the StartTime of an Event and return // the path to its location on the filesystem public function getEventPath( $event ) { - return $event['Event']['MonitorId'].'/'.strftime( "%y/%m/%d/%H/%M/%S", strtotime($event['Event']['StartTime']) ); - } + if ( $config['ZM_USE_DEEP_STORAGE'] == 1 ) + return $event['Event']['MonitorId'].'/'.strftime( "%y/%m/%d/%H/%M/%S", strtotime($event['Event']['StartTime']) ); + else + return $event['Event']['MonitorId'].'/'.$event['Event']['Id']; + } } ?> diff --git a/web/api/lib/Cake/Test/Fixture/sample.xml b/web/api/lib/Cake/Test/Fixture/sample.xml index 0ab267da0..c4eb00084 100644 --- a/web/api/lib/Cake/Test/Fixture/sample.xml +++ b/web/api/lib/Cake/Test/Fixture/sample.xml @@ -1,4 +1,4 @@ - + defect diff --git a/web/api/lib/Cake/bootstrap.php b/web/api/lib/Cake/bootstrap.php.in similarity index 99% rename from web/api/lib/Cake/bootstrap.php rename to web/api/lib/Cake/bootstrap.php.in index b17c9f68f..b4fb6d5f3 100644 --- a/web/api/lib/Cake/bootstrap.php +++ b/web/api/lib/Cake/bootstrap.php.in @@ -18,6 +18,9 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ +// Force Cake's temp folder = ZoneMinder's temp folder +define('TMP', '@ZM_TMPDIR@'); + define('TIME_START', microtime(true)); if (!defined('E_DEPRECATED')) { diff --git a/web/graphics/spinner.gif b/web/graphics/spinner.gif deleted file mode 100644 index 53dd589fa..000000000 Binary files a/web/graphics/spinner.gif and /dev/null differ diff --git a/web/graphics/spinner.png b/web/graphics/spinner.png new file mode 100644 index 000000000..0388e02f0 Binary files /dev/null and b/web/graphics/spinner.png differ diff --git a/web/graphics/transparent.gif b/web/graphics/transparent.gif deleted file mode 100644 index edab8f2b5..000000000 Binary files a/web/graphics/transparent.gif and /dev/null differ diff --git a/web/graphics/transparent.png b/web/graphics/transparent.png new file mode 100644 index 000000000..1748734dc Binary files /dev/null and b/web/graphics/transparent.png differ diff --git a/web/includes/Event.php b/web/includes/Event.php index e9ecd4bae..f4bf4a3da 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -29,12 +29,15 @@ class Event { Error('No row for Event ' . $IdOrRow ); } } // end function __construct + public function Storage() { return new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); } + public function Monitor() { return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL ); } + public function __call( $fn, array $args){ if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; @@ -54,6 +57,7 @@ class Event { $Storage = $this->Storage(); return $Storage->Path().'/'.$this->Relative_Path(); } + public function Relative_Path() { $event_path = ''; @@ -126,8 +130,10 @@ class Event { } # ! ZM_OPT_FAST_DELETE } # end Event->delete - public function getStreamSrc( $args, $querySep='&' ) { - return ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'}; + public function getStreamSrc( $args=array(), $querySep='&' ) { + if ( $this->{'DefaultVideo'} ) { + return ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'}; + } $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; @@ -195,20 +201,26 @@ class Event { return( $thumbData ); } // end function createListThumbnail + // frame is an array representing the db row for a frame. function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) { - $Storage = new Storage( $this->{'StorageId'} ); + $Storage = new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); $Event = $this; $eventPath = $Event->Path(); - if ( !is_array($frame) ) + if ( $frame and ! is_array($frame) ) { + # Must be an Id + Debug("Assuming that $frame is an Id"); $frame = array( 'FrameId'=>$frame, 'Type'=>'' ); + } - if ( file_exists( $eventPath.'/snapshot.jpg' ) ) { - $captImage = "snapshot.jpg"; + if ( ( ! $frame ) and file_exists( $eventPath.'/snapshot.jpg' ) ) { + # No frame specified, so look for a snapshot to use + $captImage = 'snapshot.jpg'; + Debug("Frame not specified, using snapshot"); } else { $captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] ); if ( ! file_exists( $eventPath.'/'.$captImage ) ) { - # Generate the frame JPG + # Generate the frame JPG if ( $Event->DefaultVideo() ) { $videoPath = $eventPath.'/'.$Event->DefaultVideo(); @@ -217,12 +229,13 @@ class Event { return ''; } - $command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage; - Debug( "Running $command" ); + #$command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage; + $command ='ffmpeg -ss '. $frame['Delta'] .' -i '.$videoPath.' -frames:v 1 '.$eventPath.'/'.$captImage; + Logger::Debug( "Running $command" ); $output = array(); $retval = 0; exec( $command, $output, $retval ); - Debug("Retval: $retval, output: " . implode("\n", $output)); + Logger::Debug("Retval: $retval, output: " . implode("\n", $output)); } else { Error("Can't create frame images from video becuase there is no video file for this event (".$Event->DefaultVideo() ); } @@ -272,8 +285,7 @@ class Event { } $thumbFile = $thumbPath; - if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) ) - { + if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { // Get new dimensions list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); $thumbWidth = $imageWidth * $fraction; @@ -287,7 +299,7 @@ class Event { if ( !imagejpeg( $thumbImage, $thumbPath ) ) Error( "Can't create thumbnail '$thumbPath'" ); } - } + } # Create thumbnails $imageData = array( 'eventPath' => $eventPath, @@ -295,7 +307,7 @@ class Event { 'thumbPath' => $thumbPath, 'imageFile' => $imagePath, 'thumbFile' => $thumbFile, - 'imageClass' => $alarmFrame?"alarm":"normal", + 'imageClass' => $alarmFrame?'alarm':'normal', 'isAnalImage' => $isAnalImage, 'hasAnalImage' => $hasAnalImage, ); diff --git a/web/includes/Frame.php b/web/includes/Frame.php index 661654d24..5a31b3dc3 100644 --- a/web/includes/Frame.php +++ b/web/includes/Frame.php @@ -6,7 +6,7 @@ class Frame { public function __construct( $IdOrRow ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit($IdOrRow) ) { $row = dbFetchOne( 'SELECT * FROM Frames WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Frame record for Id=" . $IdOrRow ); @@ -27,14 +27,16 @@ class Frame { Error("No row for Frame " . $IdOrRow ); } } // end function __construct + public function Storage() { return $this->Event()->Storage(); } + public function Event() { return new Event( $this->{'EventId'} ); } public function __call( $fn, array $args){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this ) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -70,7 +72,9 @@ class Frame { } public function getImageSrc( $show='capture' ) { - return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show;; + + return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'FrameId'}.'&eid='.$this->{'EventId'}.'&show='.$show; + #return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc public static function find( $parameters = array(), $limit = NULL ) { @@ -84,7 +88,15 @@ class Frame { $values = array_values( $parameters ); } if ( $limit ) { - $sql .= ' LIMIT ' . $limit; + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Frame::find from $file:$line"); + return; + } } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 6b983018e..9e4c6a95a 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -3,94 +3,238 @@ require_once( 'database.php' ); require_once( 'Server.php' ); class Monitor { - public function __construct( $IdOrRow ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); - if ( ! $row ) { - Error("Unable to load Server record for Id=" . $IdOrRow ); - } - } elseif ( is_array( $IdOrRow ) ) { - $row = $IdOrRow; - } else { - Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( $this->{'Controllable'} ) { - $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); - foreach ($s as $k => $v) { - if ( $k == 'Id' ) { - continue; - } - $this->{$k} = $v; - } - } +private $control_fields = array( + 'Name' => '', + 'Type' => 'Local', + 'Protocol' => NULL, + 'CanWake' => '0', + 'CanSleep' => '0', + 'CanReset' => '0', + 'CanZoom' => '0', + 'CanAutoZoom' => '0', + 'CanZoomAbs' => '0', + 'CanZoomRel' => '0', + 'CanZoomCon' => '0', + 'MinZoomRange' => NULL, + 'MaxZoomRange' => NULL, + 'MinZoomStep' => NULL, + 'MaxZoomStep' => NULL, + 'HasZoomSpeed' => '0', + 'MinZoomSpeed' => NULL, + 'MaxZoomSpeed' => NULL, + 'CanFocus' => '0', + 'CanAutoFocus' => '0', + 'CanFocusAbs' => '0', + 'CanFocusRel' => '0', + 'CanFocusCon' => '0', + 'MinFocusRange' => NULL, + 'MaxFocusRange' => NULL, + 'MinFocusStep' => NULL, + 'MaxFocusStep' => NULL, + 'HasFocusSpeed' => '0', + 'MinFocusSpeed' => NULL, + 'MaxFocusSpeed' => NULL, + 'CanIris' => '0', + 'CanAutoIris' => '0', + 'CanIrisAbs' => '0', + 'CanIrisRel' => '0', + 'CanIrisCon' => '0', + 'MinIrisRange' => NULL, + 'MaxIrisRange' => NULL, + 'MinIrisStep' => NULL, + 'MaxIrisStep' => NULL, + 'HasIrisSpeed' => '0', + 'MinIrisSpeed' => NULL, + 'MaxIrisSpeed' => NULL, + 'CanGain' => '0', + 'CanAutoGain' => '0', + 'CanGainAbs' => '0', + 'CanGainRel' => '0', + 'CanGainCon' => '0', + 'MinGainRange' => NULL, + 'MaxGainRange' => NULL, + 'MinGainStep' => NULL, + 'MaxGainStep' => NULL, + 'HasGainSpeed' => '0', + 'MinGainSpeed' => NULL, + 'MaxGainSpeed' => NULL, + 'CanWhite' => '0', + 'CanAutoWhite' => '0', + 'CanWhiteAbs' => '0', + 'CanWhiteRel' => '0', + 'CanWhiteCon' => '0', + 'MinWhiteRange' => NULL, + 'MaxWhiteRange' => NULL, + 'MinWhiteStep' => NULL, + 'MaxWhiteStep' => NULL, + 'HasWhiteSpeed' => '0', + 'MinWhiteSpeed' => NULL, + 'MaxWhiteSpeed' => NULL, + 'HasPresets' => '0', + 'NumPresets' => '0', + 'HasHomePreset' => '0', + 'CanSetPresets' => '0', + 'CanMove' => '0', + 'CanMoveDiag' => '0', + 'CanMoveMap' => '0', + 'CanMoveAbs' => '0', + 'CanMoveRel' => '0', + 'CanMoveCon' => '0', + 'CanPan' => '0', + 'MinPanRange' => NULL, + 'MaxPanRange' => NULL, + 'MinPanStep' => NULL, + 'MaxPanStep' => NULL, + 'HasPanSpeed' => '0', + 'MinPanSpeed' => NULL, + 'MaxPanSpeed' => NULL, + 'HasTurboPan' => '0', + 'TurboPanSpeed' => NULL, + 'CanTilt' => '0', + 'MinTiltRange' => NULL, + 'MaxTiltRange' => NULL, + 'MinTiltStep' => NULL, + 'MaxTiltStep' => NULL, + 'HasTiltSpeed' => '0', + 'MinTiltSpeed' => NULL, + 'MaxTiltSpeed' => NULL, + 'HasTurboTilt' => '0', + 'TurboTiltSpeed' => NULL, + 'CanAutoScan' => '0', + 'NumScanPaths' => '0', +); - } else { - Error("No row for Monitor " . $IdOrRow ); - } - } // end function __construct - public function Server() { - return new Server( $this->{'ServerId'} ); - } - public function __call( $fn, array $args){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); + public function __construct( $IdOrRow = NULL ) { + if ( $IdOrRow ) { + $row = NULL; + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Server record for Id=" . $IdOrRow ); } - } - public function getStreamSrc( $args, $querySep='&' ) { - if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { - $Server = new Server( $this->{'ServerId'} ); - $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } else { + Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); + return; + } + + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + if ( $this->{'Controllable'} ) { + $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); + foreach ($s as $k => $v) { + if ( $k == 'Id' ) { + continue; +# The reason for these is that the name overlaps Monitor fields. + } else if ( $k == 'Protocol' ) { + $this->{'ControlProtocol'} = $v; + } else if ( $k == 'Name' ) { + $this->{'ControlName'} = $v; + } else if ( $k == 'Type' ) { + $this->{'ControlType'} = $v; + } else { + $this->{$k} = $v; + } + } + } + + } else { + Error('No row for Monitor ' . $IdOrRow ); + } + } # end if isset($IdOrRow) + } // end function __construct + public function Server() { + return new Server( $this->{'ServerId'} ); + } + public function __call( $fn, array $args){ + if ( count( $args ) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists( $fn, $this ) ) { + return $this->{$fn}; + #array_unshift($args, $this); + #call_user_func_array( $this->{$fn}, $args); } else { - $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; - } + if ( array_key_exists( $fn, $this->control_fields ) ) { + return $this->control_fields{$fn}; + } else { - $args[] = "monitor=".$this->{'Id'}; + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Warning( "Unknown function call Monitor->$fn from $file:$line" ); + } + } + } - if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; - } - } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; - } - if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); - } + public function getStreamSrc( $args, $querySep='&' ) { + if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { + $Server = new Server( $this->{'ServerId'} ); + $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } else { + $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; + } - if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); - } + $args['monitor'] = $this->{'Id'}; - return( $streamSrc ); - } // end function etStreamSrc - public function Width() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Height'}; - } - return $this->{'Width'}; - } - public function Height() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Width'}; - } - return $this->{'Height'}; - } + if ( ZM_OPT_USE_AUTH ) { + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args['user'] = $_SESSION['username']; + $args['pass'] = $_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $args['user'] = $_SESSION['username']; + } + } + if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { + $args['connkey'] = $GLOBALS['connkey']; + } + if ( ZM_RAND_STREAM ) { + $args['rand'] = time(); + } + + if ( count($args) ) { + $streamSrc .= '?'.http_build_query( $args,'', $querySep ); + } + + return( $streamSrc ); + } // end function getStreamSrc + + public function Width() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Height'}; + } + return $this->{'Width'}; + } + + public function Height() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Width'}; + } + return $this->{'Height'}; + } + + public function set( $data ) { + foreach ($data as $k => $v) { + if ( is_array( $v ) ) { + # perhaps should turn into a comma-separated string + $this->{$k} = implode(',',$v); + } else if ( is_string( $v ) ) { + $this->{$k} = trim( $v ); + } else if ( is_integer( $v ) ) { + $this->{$k} = $v; + } else if ( is_bool( $v ) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } + } } ?> diff --git a/web/includes/Server.php b/web/includes/Server.php index dfce67eb8..fa892f8d9 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -5,7 +5,7 @@ class Server { public function __construct( $IdOrRow = NULL ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit( $IdOrRow ) ) { $row = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Server record for Id=" . $IdOrRow ); @@ -47,7 +47,7 @@ class Server { return $this->{'Name'}; } public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -63,9 +63,15 @@ class Server { ) ); $values = array_values( $parameters ); } - if ( $limit ) { - $sql .= ' LIMIT ' . $limit; - } + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Server::find from $file:$line"); + return; + } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { return array_map( function($id){ return new Server($id); }, $results ); diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 546cb9cbe..940653404 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -1,51 +1,89 @@ $v) { - $this->{$k} = $v; - } - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - } + public function __construct( $IdOrRow = NULL ) { + $row = NULL; + if ( $IdOrRow ) { + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Storage record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } } + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + $this->{'Name'} = ''; + $this->{'Path'} = ''; + } + } - public function Path() { - if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { - return $this->{'Path'}; - } else if ( ! isset($this->{'Id'}) ) { - return ZM_DIR_EVENTS; - } - return $this->{'Name'}; - } - public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); - } + public function Path() { + if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { + return $this->{'Path'}; + } else if ( ! isset($this->{'Id'}) ) { + $path = ZM_DIR_EVENTS; + if ( $path[0] != '/' ) { + $this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS; + } else { + $this->{'Path'} = ZM_DIR_EVENTS; + } + return $this->{'Path'}; + } - public static function find_all() { - $storage_areas = array(); - $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); - foreach ( $results as $row => $obj ) { - $storage_areas[] = $obj; - } - return $storage_areas; + return $this->{'Name'}; + } + public function Name() { + if ( isset( $this->{'Name'} ) and ( $this->{'Name'} != '' ) ) { + return $this->{'Name'}; + } else if ( ! isset($this->{'Id'}) ) { + return 'Default'; } + return $this->{'Name'}; + } + + public function __call( $fn, array $args= NULL){ + if(isset($this->{$fn})){ + return $this->{$fn}; +#array_unshift($args, $this); +#call_user_func_array( $this->{$fn}, $args); + } + } + public static function find_all() { + $storage_areas = array(); + $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); + foreach ( $results as $row => $obj ) { + $storage_areas[] = $obj; + } + return $storage_areas; + } + public function disk_usage_percent() { + $path = $this->Path(); + if ( ! $path ) { + Warning("Storage::disk_usage_percent: path is empty"); + return 0; + } else if ( ! file_exists( $path ) ) { + Warning("Storage::disk_usage_percent: path $path does not exist"); + return 0; + } + + $total = disk_total_space( $path ); + if ( ! $total ) { + Error("disk_total_space returned false for " . $path ); + return 0; + } + $free = disk_free_space( $path ); + if ( ! $free ) { + Error("disk_free_space returned false for " . $path ); + } + $usage = round(($total - $free) / $total * 100); + return $usage; + } } ?> diff --git a/web/includes/actions.php b/web/includes/actions.php index 8c1300f37..d306c3bbc 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -23,12 +23,11 @@ // credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/ -function do_post_request($url, $data, $optional_headers = null) -{ +function do_post_request($url, $data, $optional_headers = null) { $params = array('http' => array( - 'method' => 'POST', - 'content' => $data - )); + 'method' => 'POST', + 'content' => $data + )); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } @@ -44,990 +43,864 @@ function do_post_request($url, $data, $optional_headers = null) return $response; } -function getAffectedIds( $name ) -{ - $names = $name."s"; - $ids = array(); - if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) - { - if ( isset($_REQUEST[$names]) ) - $ids = validInt($_REQUEST[$names]); - else if ( isset($_REQUEST[$name]) ) - $ids[] = validInt($_REQUEST[$name]); - } - return( $ids ); +function getAffectedIds( $name ) { + $names = $name.'s'; + $ids = array(); + if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) { + if ( isset($_REQUEST[$names]) ) + $ids = validInt($_REQUEST[$names]); + else if ( isset($_REQUEST[$name]) ) + $ids[] = validInt($_REQUEST[$name]); + } + return( $ids ); } -if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) -{ - if ( $authUser = getAuthUser( $_REQUEST['auth'] ) ) + +if ( !empty($action) ) { + if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { + // if true, a popup will display after login + // PP - lets validate reCaptcha if it exists + if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') + && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') + && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') + && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY + && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) { - userLogin( $authUser['Username'], $authUser['Password'], true ); - } -} - -if ( !empty($action) ) -{ - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - // if true, a popup will display after login - // PP - lets validate reCaptcha if it exists - if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') - && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') - && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') - && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY - && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) - { - $url = 'https://www.google.com/recaptcha/api/siteverify'; - $fields = array ( - 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, - 'response' => $_REQUEST['g-recaptcha-response'], - 'remoteip'=> $_SERVER['REMOTE_ADDR'] - - ); - $res= do_post_request($url, http_build_query($fields)); - $responseData = json_decode($res,true); - // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php - // if recaptcha resulted in error, we might have to deny login - if (isset($responseData['success']) && $responseData['success'] == false) - { - // PP - before we deny auth, let's make sure the error was not 'invalid secret' - // because that means the user did not configure the secret key correctly - // in this case, we prefer to let him login in and display a message to correct - // the key. Unfortunately, there is no way to check for invalid site key in code - // as it produces the same error as when you don't answer a recaptcha - if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) - { - if (!in_array('invalid-input-secret',$responseData['error-codes'])) - { - Error ("reCaptcha authentication failed"); - userLogout(); - $view='login'; - $refreshParent = true; - } - else - { - //Let them login but show an error - echo ''; - Error ("Invalid recaptcha secret detected"); - - } - } - - } - - } - } - - // General scope actions - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - $username = validStr( $_REQUEST['username'] ); - $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; - userLogin( $username, $password ); - } - elseif ( $action == "logout" ) - { - userLogout(); - $refreshParent = true; - $view = 'none'; - } - elseif ( $action == "bandwidth" && isset($_REQUEST['newBandwidth']) ) - { - $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); - setcookie( "zmBandwidth", validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); - $refreshParent = true; - } - - // Event scope actions, view permissions only required - if ( canView( 'Events' ) ) - { - if ( $action == "filter" ) - { - if ( !empty($_REQUEST['subaction']) ) - { - if ( $_REQUEST['subaction'] == "addterm" ) - $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - elseif ( $_REQUEST['subaction'] == "delterm" ) - $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - } - elseif ( canEdit( 'Events' ) ) - { - if ( !empty($_REQUEST['execute']) ) - $tempFilterName = "_TempFilter".time(); - if ( isset($tempFilterName) ) - $filterName = $tempFilterName; - elseif ( !empty($_REQUEST['newFilterName']) ) - $filterName = $_REQUEST['newFilterName']; - if ( !empty($filterName) ) - { - $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); - $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); - $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); - $sql = "replace into Filters set Name = ".dbEscape($filterName).", Query = ".dbEscape(jsonEncode($_REQUEST['filter'])); - if ( !empty($_REQUEST['AutoArchive']) ) - $sql .= ", AutoArchive = ".dbEscape($_REQUEST['AutoArchive']); - if ( !empty($_REQUEST['AutoVideo']) ) - $sql .= ", AutoVideo = ".dbEscape($_REQUEST['AutoVideo']); - if ( !empty($_REQUEST['AutoUpload']) ) - $sql .= ", AutoUpload = ".dbEscape($_REQUEST['AutoUpload']); - if ( !empty($_REQUEST['AutoEmail']) ) - $sql .= ", AutoEmail = ".dbEscape($_REQUEST['AutoEmail']); - if ( !empty($_REQUEST['AutoMessage']) ) - $sql .= ", AutoMessage = ".dbEscape($_REQUEST['AutoMessage']); - if ( !empty($_REQUEST['AutoExecute']) && !empty($_REQUEST['AutoExecuteCmd']) ) - $sql .= ", AutoExecute = ".dbEscape($_REQUEST['AutoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['AutoExecuteCmd']); - if ( !empty($_REQUEST['AutoDelete']) ) - $sql .= ", AutoDelete = ".dbEscape($_REQUEST['AutoDelete']); - if ( !empty($_REQUEST['background']) ) - $sql .= ", Background = ".dbEscape($_REQUEST['background']); - dbQuery( $sql ); - $refreshParent = true; - } - } - } - } - - // Event scope actions, edit permissions required - if ( canEdit( 'Events' ) ) - { - if ( $action == "rename" && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); - } - else if ( $action == "eventdetail" ) - { - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); - $refreshParent = true; - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "archive" || $action == "unarchive" ) - { - $archiveVal = ($action == "archive")?1:0; - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "delete" ) - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - deleteEvent( $markEid ); - $refreshParent = true; - } - if ( !empty($_REQUEST['fid']) ) - { - dbQuery( 'DELETE FROM Filters WHERE Name=?', array( $_REQUEST['fid'] ) ); - //$refreshParent = true; - } - } - } - - // Monitor control actions, require a monitor id and control view permissions for that monitor - if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) - { - require_once( 'control_functions.php' ); - require_once( 'Monitor.php' ); - $mid = validInt($_REQUEST['mid']); - if ( $action == "control" ) - { - $monitor = new Monitor( $mid ); - - $ctrlCommand = buildControlCommand( $monitor ); - sendControlCommand( $monitor->Id(), $ctrlCommand ); - } - elseif ( $action == "settings" ) - { - $args = " -m " . escapeshellarg($mid); - $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); - $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); - $args .= " -H" . escapeshellarg($_REQUEST['newHue']); - $args .= " -O" . escapeshellarg($_REQUEST['newColour']); - - $zmuCommand = getZmuCommand( $args ); - - $zmuOutput = exec( $zmuCommand ); - list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); - dbQuery( "update Monitors set Brightness = ?, Contrast = ?, Hue = ?, Colour = ? where Id = ?", array($brightness, $contrast, $hue, $colour, $mid)); - } - } - - // Control capability actions, require control edit permissions - if ( canEdit( 'Control' ) ) - { - if ( $action == "controlcap" ) - { - if ( !empty($_REQUEST['cid']) ) - { - $control = dbFetchOne( "select * from Controls where Id = ?", NULL, array($_REQUEST['cid']) ); - } - else - { - $control = array(); - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - // Empty - ); - - $columns = getTableColumns( 'Controls' ); - foreach ( $columns as $name=>$type ) - { - if ( preg_match( '/^(Can|Has)/', $name ) ) - { - $types[$name] = 'toggle'; - } - } - $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['cid']) ) - { - dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); - } - else - { - dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); - //$_REQUEST['cid'] = dbInsertId(); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markCids']) ) - { - foreach( $_REQUEST['markCids'] as $markCid ) - { - dbQuery( "delete from Controls where Id = ?", array($markCid) ); - dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); - $refreshParent = true; - } - } - } - } - - // Monitor edit actions, require a monitor id and edit permissions for that monitor - if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) - { - $mid = validInt($_REQUEST['mid']); - if ( $action == "function" ) - { - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - $newFunction = validStr($_REQUEST['newFunction']); - # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 - $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; - - $oldFunction = $monitor['Function']; - $oldEnabled = $monitor['Enabled']; - if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) - { - dbQuery( "update Monitors set Function=?, Enabled=? where Id=?", array( $newFunction, $newEnabled, $mid ) ); - - $monitor['Function'] = $newFunction; - $monitor['Enabled'] = $newEnabled; - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, $restart?"restart":"" ); - zmaControl( $monitor, "start" ); - } - $refreshParent = true; - } - } - elseif ( $action == "zone" && isset( $_REQUEST['zid'] ) ) - { - $zid = validInt($_REQUEST['zid']); - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - if ( !empty($zid) ) - { - $zone = dbFetchOne( "SELECT * FROM Zones WHERE MonitorId=? AND Id=?", NULL, array( $mid, $zid ) ); - } - else - { - $zone = array(); - } - - if ( $_REQUEST['newZone']['Units'] == 'Percent' ) - { - $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) - $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) - $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) - $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) - $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); - } - - unset( $_REQUEST['newZone']['Points'] ); - $types = array(); - $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); - - if ( count( $changes ) ) - { - if ( $zid > 0 ) - { - dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); - } - else - { - dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); - } - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - } - if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "plugin" && isset($_REQUEST['pl'])) - { - $sql="SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?"; - $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); - $changes=0; - foreach( $pconfs as $pconf ) - { - $value=$_REQUEST['pluginOpt'][$pconf['Name']]; - if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) - { - dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); - $changes++; - } - } - if($changes>0) - { - if ( daemonCheck() ) - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "sequence" && isset($_REQUEST['smid']) ) - { - $smid = validInt($_REQUEST['smid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - $smonitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($smid) ); - - dbQuery( "update Monitors set Sequence=? where Id=?", array( $smonitor['Sequence'], $monitor['Id'] ) ); - dbQuery( "update Monitors set Sequence=? WHERE Id=?", array( $monitor['Sequence'], $smonitor['Id'] ) ); - + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = array ( + 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, + 'response' => $_REQUEST['g-recaptcha-response'], + 'remoteip'=> $_SERVER['REMOTE_ADDR'] + ); + $res= do_post_request($url, http_build_query($fields)); + $responseData = json_decode($res,true); + // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php + // if recaptcha resulted in error, we might have to deny login + if (isset($responseData['success']) && $responseData['success'] == false) { + // PP - before we deny auth, let's make sure the error was not 'invalid secret' + // because that means the user did not configure the secret key correctly + // in this case, we prefer to let him login in and display a message to correct + // the key. Unfortunately, there is no way to check for invalid site key in code + // as it produces the same error as when you don't answer a recaptcha + if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { + if (!in_array('invalid-input-secret',$responseData['error-codes'])) { + Error ('reCaptcha authentication failed'); + userLogout(); + $view='login'; $refreshParent = true; - fixSequences(); + } else { + //Let them login but show an error + echo ''; + Error ("Invalid recaptcha secret detected"); + } } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markZids']) ) - { - $deletedZid = 0; - foreach( $_REQUEST['markZids'] as $markZid ) - { - $zone = dbFetchOne( "select * from Zones where Id=?", NULL, array($markZid) ); - dbQuery( "delete from Zones WHERE MonitorId=? AND Id=?", array( $mid, $markZid) ); - $deletedZid = 1; - } - if ( $deletedZid ) - { - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - if ( $zone['Type'] == 'Privacy' ) - { - zmaControl( $mid, "stop" ); - zmcControl( $mid, "restart" ); - zmaControl( $mid, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - } + } // end if success==false + + } // end if using reCaptcha + + $username = validStr( $_REQUEST['username'] ); + $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; + userLogin( $username, $password ); + $refreshParent = true; + $view = 'console'; + $redirect = true; + } else if ( $action == 'logout' ) { + userLogout(); + $refreshParent = true; + $view = 'none'; + } else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { + $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); + setcookie( 'zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); + $refreshParent = true; + } + + // Event scope actions, view permissions only required + if ( canView( 'Events' ) ) { + + if ( $action == 'filter' ) { + if ( !empty($_REQUEST['subaction']) ) { + if ( $_REQUEST['subaction'] == 'addterm' ) + $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + elseif ( $_REQUEST['subaction'] == 'delterm' ) + $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + } elseif ( canEdit( 'Events' ) ) { + $sql = ''; + $endSql = ''; + $filterName = ''; + if ( !empty($_REQUEST['execute']) ) { + // TempFilterName is used in event listing later on + $tempFilterName = $filterName = '_TempFilter'.time(); + } elseif ( !empty($_REQUEST['newFilterName']) ) { + $filterName = $_REQUEST['newFilterName']; } - } - - // Monitor edit actions, monitor id derived, require edit permissions for that monitor - if ( canEdit( 'Monitors' ) ) - { - if ( $action == "monitor" ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - - if ( ZM_OPT_X10 ) - { - $x10Monitor = dbFetchOne( "select * from TriggersX10 where MonitorId=?", NULL, array($mid) ); - if ( !$x10Monitor ) - $x10Monitor = array(); - } - } - else - { - $monitor = array(); - if ( ZM_OPT_X10 ) - { - $x10Monitor = array(); - } - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - 'Triggers' => 'set', - 'Controllable' => 'toggle', - 'TrackMotion' => 'toggle', - 'Enabled' => 'toggle', - 'DoNativeMotDet' => 'toggle', - 'Exif' => 'toggle', - 'RTSPDescribe' => 'toggle', - ); - - $columns = getTableColumns( 'Monitors' ); - $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - dbQuery( "update Monitors set ".implode( ", ", $changes )." where Id =?", array($mid) ); - if ( isset($changes['Name']) ) - { - $saferOldName = basename( $monitor['Name'] ); - $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); - rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); - } - if ( isset($changes['Width']) || isset($changes['Height']) ) - { - $newW = $_REQUEST['newMonitor']['Width']; - $newH = $_REQUEST['newMonitor']['Height']; - $newA = $newW * $newH; - $oldW = $monitor['Width']; - $oldH = $monitor['Height']; - $oldA = $oldW * $oldH; - - $zones = dbFetchAll( "select * from Zones where MonitorId=?", NULL, array($mid) ); - foreach ( $zones as $zone ) - { - $newZone = $zone; - $points = coordsToPoints( $zone['Coords'] ); - for ( $i = 0; $i < count($points); $i++ ) - { - $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); - $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); - } - $newZone['Coords'] = pointsToCoords( $points ); - $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); - $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); - $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); - $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); - $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); - $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); - $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); - - $changes = getFormChanges( $zone, $newZone, $types ); - - if ( count( $changes ) ) - { - dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); - } - } - } - } - elseif ( !$user['MonitorIds'] ) - { - # FIXME This is actually a race condition. Should lock the table. - $maxSeq = dbFetchOne( "select max(Sequence) as MaxSequence from Monitors", "MaxSequence" ); - $changes[] = "Sequence = ".($maxSeq+1); - - dbQuery( "insert into Monitors set ".implode( ", ", $changes ) ); - $mid = dbInsertId(); - $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; - dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); - //$view = 'none'; - mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); - $saferName = basename($_REQUEST['newMonitor']['Name']); - symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); - if ( isset($_COOKIE['zmGroup']) ) - { - dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); - } - } - $restart = true; - } - - if ( ZM_OPT_X10 ) - { - $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); - - if ( count( $x10Changes ) ) - { - if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) - { - dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); - } - elseif ( !$user['MonitorIds'] ) - { - if ( !$x10Monitor ) - { - dbQuery( "insert into TriggersX10 set MonitorId = ?".implode( ", ", $x10Changes ), array( $mid ) ); - } - else - { - dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); - } - } - $restart = true; - } - } - - if ( $restart ) - { - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - //fixDevices(); - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - if ( $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - //daemonControl( 'restart', 'zmwatch.pl' ); - $refreshParent = true; - } - $view = 'none'; - } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) - { - foreach( $_REQUEST['markMids'] as $markMid ) - { - if ( canEdit( 'Monitors', $markMid ) ) - { - if ( $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($markMid) ) ) - { - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "stop" ); - } - - // This is the important stuff - dbQuery( "delete from Monitors where Id = ?", array($markMid) ); - dbQuery( "delete from Zones where MonitorId = ?", array($markMid) ); - if ( ZM_OPT_X10 ) - dbQuery( "delete from TriggersX10 where MonitorId=?", array($markMid) ); - - fixSequences(); - - // If fast deletes are on, then zmaudit will clean everything else up later - // If fast deletes are off and there are lots of events then this step may - // well time out before completing, in which case zmaudit will still tidy up - if ( !ZM_OPT_FAST_DELETE ) - { - // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. - $markEids = dbFetchAll( "SELECT Id,StartTime FROM Events WHERE MonitorId=?", NULL, array($markMid) ); - foreach( $markEids as $markEid ) - deleteEvent( $markEid, $markMid ); - - deletePath( ZM_DIR_EVENTS."/".basename($monitor['Name']) ); - deletePath( ZM_DIR_EVENTS."/".$monitor['Id'] ); // I'm trusting the Id. - } - } - } - } - } - } - } - - // Device view actions - if ( canEdit( 'Devices' ) ) - { - if ( $action == "device" ) - { - if ( !empty($_REQUEST['command']) ) - { - setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); - } - elseif ( isset( $_REQUEST['newDevice'] ) ) - { - if ( isset($_REQUEST['did']) ) - { - dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); - } - else - { - dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); - } - $refreshParent = true; - $view = 'none'; - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markDids']) ) - { - foreach( $_REQUEST['markDids'] as $markDid ) - { - dbQuery( "delete from Devices where Id=?", array($markDid) ); - $refreshParent = true; - } - } - } - } - - // Group view actions - if ( canView( 'Groups' ) && $action == "setgroup" ) { - if ( !empty($_REQUEST['gid']) ) { - setcookie( "zmGroup", validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + if ( $filterName ) { + # Replace will teplace any filter with the same Id + # Since we aren't specifying the Id , this is effectively an insert + $sql = 'REPLACE INTO Filters SET Name = '.dbEscape($filterName).','; } else { - setcookie( "zmGroup", "", time()-3600*24*2 ); + $sql = 'UPDATE Filters SET'; + $endSql = 'WHERE Id = '.$_REQUEST['Id']; } - $refreshParent = true; - } - // Group edit actions - if ( canEdit( 'Groups' ) ) { - if ( $action == "group" ) { - # Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this - $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); - if ( !empty($_POST['gid']) ) { - dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + # endSql is only set if ! filterName... so... woulnd't this always be true + if ( !empty($filterName) || $endSql ) { + $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); + $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); + $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); + $sql .= ' Query = '.dbEscape(jsonEncode($_REQUEST['filter'])); + if ( !empty($_REQUEST['AutoArchive']) ) + $sql .= ', AutoArchive = '.dbEscape($_REQUEST['AutoArchive']); + if ( !empty($_REQUEST['AutoVideo']) ) + $sql .= ', AutoVideo = '.dbEscape($_REQUEST['AutoVideo']); + if ( !empty($_REQUEST['AutoUpload']) ) + $sql .= ', AutoUpload = '.dbEscape($_REQUEST['AutoUpload']); + if ( !empty($_REQUEST['AutoEmail']) ) + $sql .= ', AutoEmail = '.dbEscape($_REQUEST['AutoEmail']); + if ( !empty($_REQUEST['AutoMessage']) ) + $sql .= ', AutoMessage = '.dbEscape($_REQUEST['AutoMessage']); + if ( !empty($_REQUEST['AutoExecute']) && !empty($_REQUEST['AutoExecuteCmd']) ) + $sql .= ', AutoExecute = '.dbEscape($_REQUEST['AutoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['AutoExecuteCmd']); + if ( !empty($_REQUEST['AutoDelete']) ) + $sql .= ', AutoDelete = '.dbEscape($_REQUEST['AutoDelete']); + if ( !empty($_REQUEST['background']) ) + $sql .= ', Background = '.dbEscape($_REQUEST['background']); + if ( !empty($_REQUEST['concurrent']) ) + $sql .= ', Concurrent = '.dbEscape($_REQUEST['concurrent']); + $sql .= $endSql; + dbQuery( $sql ); + if ( $filterName ) { + $filter = dbFetchOne( 'SELECT * FROM Filters WHERE Name=?', NULL, array($filterName) ); + if ( $filter ) { + # This won't work yet because refreshparent refreshes the old filter. Need to do a redirect instead of a refresh. + $_REQUEST['Id'] = $filter['Id']; } else { - dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + Error("No new Id despite new name"); } - $view = 'none'; + } + $refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id']; } - if ( !empty($_REQUEST['gid']) && $action == "delete" ) { - dbQuery( "delete from Groups where Id = ?", array($_REQUEST['gid']) ); - if ( isset($_COOKIE['zmGroup']) ) - { - if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) - { - unset( $_COOKIE['zmGroup'] ); - setcookie( "zmGroup", "", time()-3600*24*2 ); - $refreshParent = true; - } - } + } // end if canedit events + } // end if action == filter + } // end if canview events + + // Event scope actions, edit permissions required + if ( canEdit( 'Events' ) ) { + if ( $action == 'rename' && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); + } else if ( $action == 'eventdetail' ) { + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); + $refreshParent = true; + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'archive' || $action == 'unarchive' ) { + $archiveVal = ($action == 'archive')?1:0; + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'delete' ) { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + deleteEvent( $markEid ); + $refreshParent = true; + } + if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { + if ( !empty($_REQUEST['Id']) ) { + dbQuery( 'DELETE FROM Filters WHERE Id=?', array( $_REQUEST['Id'] ) ); + //$refreshParent = true; + } + } + } + } + + // Monitor control actions, require a monitor id and control view permissions for that monitor + if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { + require_once( 'control_functions.php' ); + require_once( 'Monitor.php' ); + $mid = validInt($_REQUEST['mid']); + if ( $action == 'control' ) { + $monitor = new Monitor( $mid ); + + $ctrlCommand = buildControlCommand( $monitor ); + sendControlCommand( $monitor->Id(), $ctrlCommand ); + } elseif ( $action == 'settings' ) { + $args = " -m " . escapeshellarg($mid); + $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); + $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); + $args .= " -H" . escapeshellarg($_REQUEST['newHue']); + $args .= " -O" . escapeshellarg($_REQUEST['newColour']); + + $zmuCommand = getZmuCommand( $args ); + + $zmuOutput = exec( $zmuCommand ); + list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); + dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); + } + } + + // Control capability actions, require control edit permissions + if ( canEdit( 'Control' ) ) { + if ( $action == 'controlcap' ) { + if ( !empty($_REQUEST['cid']) ) { + $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); + } else { + $control = array(); + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + // Empty + ); + + $columns = getTableColumns( 'Controls' ); + foreach ( $columns as $name=>$type ) { + if ( preg_match( '/^(Can|Has)/', $name ) ) { + $types[$name] = 'toggle'; + } + } + $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['cid']) ) { + dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); + } else { + dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); + //$_REQUEST['cid'] = dbInsertId(); } $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markCids']) ) { + foreach( $_REQUEST['markCids'] as $markCid ) { + dbQuery( "delete from Controls where Id = ?", array($markCid) ); + dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); + $refreshParent = true; + } + } } + } - // System edit actions - if ( canEdit( 'System' ) ) - { - if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'server' ) ) { + // Monitor edit actions, require a monitor id and edit permissions for that monitor + if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { + $mid = validInt($_REQUEST['mid']); + if ( $action == 'function' ) { + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); - if ( $action == "Save" ) { - if ( !empty($_REQUEST['id']) ) - $dbServer = dbFetchOne( "SELECT * FROM Servers WHERE Id=?", NULL, array($_REQUEST['id']) ); - else - $dbServer = array(); + $newFunction = validStr($_REQUEST['newFunction']); + # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 + $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; + $oldFunction = $monitor['Function']; + $oldEnabled = $monitor['Enabled']; + if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { + dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) ); - $types = array(); - $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['id']) ) { - dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); - } else { - dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['markIds']) ) { - foreach( $_REQUEST['markIds'] as $Id ) - dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); - } - $refreshParent = true; - } else { - Error( "Unknown action $action in saving Server" ); - } - - } else if ( $action == "version" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'ignore' : - { - dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); - break; - } - case 'hour' : - case 'day' : - case 'week' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); - break; - } - case 'never' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); - break; - } - } + $monitor['Function'] = $newFunction; + $monitor['Enabled'] = $newEnabled; + if ( daemonCheck() ) { + $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, $restart?'restart':'' ); + zmaControl( $monitor, 'start' ); } - if ( $action == "donate" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'hour' : - case 'day' : - case 'week' : - case 'month' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - elseif ( $option == 'month' ) - { - $nextReminder += 30*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); - break; - } - case 'never' : - case 'already' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); - break; - } - } + $refreshParent = true; + } + } elseif ( $action == 'zone' && isset( $_REQUEST['zid'] ) ) { + $zid = validInt($_REQUEST['zid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); + + if ( !empty($zid) ) { + $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array( $mid, $zid ) ); + } else { + $zone = array(); + } + + if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { + $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) + $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) + $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) + $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) + $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); + } + + unset( $_REQUEST['newZone']['Points'] ); + $types = array(); + $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); + + if ( count( $changes ) ) { + if ( $zid > 0 ) { + dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); + } else { + dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); } - if ( $action == "options" && isset($_REQUEST['tab']) ) - { - $configCat = $configCats[$_REQUEST['tab']]; - $changed = false; - foreach ( $configCat as $name=>$value ) - { - unset( $newValue ); - if ( $value['Type'] == "boolean" && empty($_REQUEST['newConfig'][$name]) ) - $newValue = 0; - elseif ( isset($_REQUEST['newConfig'][$name]) ) - $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); - - if ( isset($newValue) && ($newValue != $value['Value']) ) - { - dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); - $changed = true; - } - } - if ( $changed ) - { - switch( $_REQUEST['tab'] ) - { - case "system" : - case "config" : - case "paths" : - $restartWarning = true; - break; - case "web" : - case "tools" : - break; - case "logging" : - case "network" : - case "mail" : - case "upload" : - $restartWarning = true; - break; - case "highband" : - case "medband" : - case "lowband" : - break; - } - } - loadConfig( false ); - } - elseif ( $action == "user" ) - { - if ( !empty($_REQUEST['uid']) ) - $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); - else - $dbUser = array(); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( $_REQUEST['newUser']['Password'] ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['uid']) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); - } - else - { - dbQuery( "insert into Users set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - if ( $dbUser['Username'] == $user['Username'] ) - userLogin( $dbUser['Username'], $dbUser['Password'] ); - } - $view = 'none'; - } - elseif ( $action == "state" ) - { - if ( !empty($_REQUEST['runState']) ) - { - //if ( $cookies ) session_write_close(); - packageControl( $_REQUEST['runState'] ); - $refreshParent = true; - } - } - elseif ( $action == "save" ) - { - if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) - { - $sql = "select Id,Function,Enabled from Monitors order by Id"; - $definitions = array(); - foreach( dbFetchAll( $sql ) as $monitor ) - { - $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; - } - $definition = join( ',', $definitions ); - if ( $_REQUEST['newState'] ) - $_REQUEST['runState'] = $_REQUEST['newState']; - dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['runState']) ) - dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); - - if ( isset($_REQUEST['markUids']) ) - { - foreach( $_REQUEST['markUids'] as $markUid ) - dbQuery( "delete from Users where Id = ?", array($markUid) ); - if ( $markUid == $user['Id'] ) - userLogout(); - } - } - } - else - { - if ( ZM_USER_SELF_EDIT && $action == "user" ) - { - $uid = $user['Id']; - - $dbUser = dbFetchOne( "select Id, Password, Language from Users where Id = ?", NULL, array($uid) ); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( !empty($_REQUEST['newUser']['Password']) ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - if ( count( $changes ) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); - $refreshParent = true; - } - $view = 'none'; - } - } - - if ( $action == "reset" ) - { - $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); - setcookie( "zmEventResetTime", $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); //if ( $cookies ) session_write_close(); + if ( daemonCheck() ) { + if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'restart' ); + zmaControl( $monitor, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } + if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'plugin' && isset($_REQUEST['pl'])) { + $sql='SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; + $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); + $changes=0; + foreach( $pconfs as $pconf ) { + $value=$_REQUEST['pluginOpt'][$pconf['Name']]; + if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) { + dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); + $changes++; + } + } + if($changes>0) { + if ( daemonCheck() ) { + zmaControl( $mid, 'restart' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) { + $smid = validInt($_REQUEST['smid']); + $monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ); + $smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) ); + + dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) ); + dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) ); + + $refreshParent = true; + fixSequences(); + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markZids']) ) { + $deletedZid = 0; + foreach( $_REQUEST['markZids'] as $markZid ) { + $zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) ); + dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) ); + $deletedZid = 1; + } + if ( $deletedZid ) { + //if ( $cookies ) + //session_write_close(); + if ( daemonCheck() ) { + if ( $zone['Type'] == 'Privacy' ) { + zmaControl( $mid, 'stop' ); + zmcControl( $mid, 'restart' ); + zmaControl( $mid, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } // end if daemonCheck() + $refreshParent = true; + } // end if deletedzid + } // end if isset($_REQUEST['markZids']) + } // end if action + } // end if $mid and canEdit($mid) + + // Monitor edit actions, monitor id derived, require edit permissions for that monitor + if ( canEdit( 'Monitors' ) ) { + if ( $action == 'monitor' ) { + $mid = 0; + if ( !empty($_REQUEST['mid']) ) { + $mid = validInt($_REQUEST['mid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + + if ( ZM_OPT_X10 ) { + $x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) ); + if ( !$x10Monitor ) + $x10Monitor = array(); + } + } else { + $monitor = array(); + if ( ZM_OPT_X10 ) { + $x10Monitor = array(); + } + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + 'Triggers' => 'set', + 'Controllable' => 'toggle', + 'TrackMotion' => 'toggle', + 'Enabled' => 'toggle', + 'DoNativeMotDet' => 'toggle', + 'Exif' => 'toggle', + 'RTSPDescribe' => 'toggle', + 'RecordAudio' => 'toggle', + ); + + $columns = getTableColumns( 'Monitors' ); + $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); + + if ( count( $changes ) ) { + if ( $mid ) { + + # If we change anything that changes the shared mem size, zma can complain. So let's stop first. + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + dbQuery( 'UPDATE Monitors SET '.implode( ", ", $changes ).' WHERE Id =?', array($mid) ); + if ( isset($changes['Name']) ) { + $saferOldName = basename( $monitor['Name'] ); + $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); + rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); + } + if ( isset($changes['Width']) || isset($changes['Height']) ) { + $newW = $_REQUEST['newMonitor']['Width']; + $newH = $_REQUEST['newMonitor']['Height']; + $newA = $newW * $newH; + $oldW = $monitor['Width']; + $oldH = $monitor['Height']; + $oldA = $oldW * $oldH; + + $zones = dbFetchAll( 'SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid) ); + foreach ( $zones as $zone ) { + $newZone = $zone; + $points = coordsToPoints( $zone['Coords'] ); + for ( $i = 0; $i < count($points); $i++ ) { + $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); + $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); + } + $newZone['Coords'] = pointsToCoords( $points ); + $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); + $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); + $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); + $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); + $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); + $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); + $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); + + $changes = getFormChanges( $zone, $newZone, $types ); + + if ( count( $changes ) ) { + dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); + } + } + } + } elseif ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors +# FIXME This is actually a race condition. Should lock the table. + $maxSeq = dbFetchOne( 'SELECT max(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence' ); + $changes[] = 'Sequence = '.($maxSeq+1); + + dbQuery( 'INSERT INTO Monitors SET '.implode( ', ', $changes ) ); + $mid = dbInsertId(); + $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; + dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); + //$view = 'none'; + mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); + $saferName = basename($_REQUEST['newMonitor']['Name']); + symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); + if ( isset($_COOKIE['zmGroup']) ) { + dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); + } + } else { + Error("Users with Monitors restrictions cannot create new monitors."); + } + $restart = true; + } + + if ( ZM_OPT_X10 ) { + $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); + + if ( count( $x10Changes ) ) { + if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { + dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); + } elseif ( !$user['MonitorIds'] ) { + if ( !$x10Monitor ) { + dbQuery( "insert into TriggersX10 set MonitorId = ?, ".implode( ", ", $x10Changes ), array( $mid ) ); + } else { + dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); + } + } + $restart = true; + } + } + + if ( $restart ) { + $new_monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + //fixDevices(); + //if ( $cookies ) + //session_write_close(); + + zmcControl( $new_monitor, 'start' ); + zmaControl( $new_monitor, 'start' ); + + if ( $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + // really should thump zmwatch and maybe zmtrigger too. + //daemonControl( 'restart', 'zmwatch.pl' ); + $refreshParent = true; + } // end if restart + $view = 'none'; } + if ( $action == 'delete' ) { + if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { + foreach( $_REQUEST['markMids'] as $markMid ) { + if ( canEdit( 'Monitors', $markMid ) ) { + if ( $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid) ) ) { + if ( daemonCheck() ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + } + + // This is the important stuff + dbQuery( 'DELETE FROM Monitors WHERE Id = ?', array($markMid) ); + dbQuery( 'DELETE FROM Zones WHERE MonitorId = ?', array($markMid) ); + if ( ZM_OPT_X10 ) + dbQuery( 'DELETE FROM TriggersX10 WHERE MonitorId=?', array($markMid) ); + + fixSequences(); + + // If fast deletes are on, then zmaudit will clean everything else up later + // If fast deletes are off and there are lots of events then this step may + // well time out before completing, in which case zmaudit will still tidy up + if ( !ZM_OPT_FAST_DELETE ) { + // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. + $markEids = dbFetchAll( 'SELECT Id,StartTime FROM Events WHERE MonitorId=?', NULL, array($markMid) ); + foreach( $markEids as $markEid ) + deleteEvent( $markEid, $markMid ); + + deletePath( ZM_DIR_EVENTS.'/'.basename($monitor['Name']) ); + deletePath( ZM_DIR_EVENTS.'/'.$monitor['Id'] ); // I'm trusting the Id. + } // end if ZM_OPT_FAST_DELETE + } // end if found the monitor in the db + } // end if canedit this monitor + } // end foreach monitor in MarkMid + } // markMids is set and we aren't limited to specific monitors + } // end if action == Delete + } + + // Device view actions + if ( canEdit( 'Devices' ) ) { + if ( $action == 'device' ) { + if ( !empty($_REQUEST['command']) ) { + setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); + } elseif ( isset( $_REQUEST['newDevice'] ) ) { + if ( isset($_REQUEST['did']) ) { + dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); + } else { + dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); + } + $refreshParent = true; + $view = 'none'; + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markDids']) ) { + foreach( $_REQUEST['markDids'] as $markDid ) { + dbQuery( "delete from Devices where Id=?", array($markDid) ); + $refreshParent = true; + } + } + } // end if action + } // end if canedit devices + + // Group view actions + if ( canView( 'Groups' ) && $action == 'setgroup' ) { + if ( !empty($_REQUEST['gid']) ) { + setcookie( 'zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + } else { + setcookie( 'zmGroup', '', time()-3600*24*2 ); + } + $refreshParent = true; + } + + // Group edit actions + if ( canEdit( 'Groups' ) ) { + if ( $action == 'group' ) { +# Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this + $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); + if ( !empty($_POST['gid']) ) { + dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + } else { + dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + } + $view = 'none'; + } + if ( !empty($_REQUEST['gid']) && $action == 'delete' ) { + dbQuery( 'DELETE FROM Groups WHERE Id = ?', array($_REQUEST['gid']) ); + if ( isset($_COOKIE['zmGroup']) ) { + if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) { + unset( $_COOKIE['zmGroup'] ); + setcookie( 'zmGroup', '', time()-3600*24*2 ); + $refreshParent = true; + } + } + } + $refreshParent = true; + } // end if can edit groups + + // System edit actions + if ( canEdit( 'System' ) ) { + if ( isset( $_REQUEST['object'] ) ) { + if ( $_REQUEST['object'] == 'server' ) { + + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbServer = array(); + + $types = array(); + $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Server" ); + } + } else if ( $_REQUEST['object'] == 'storage' ) { + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbStorage = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbStorage = array(); + + $types = array(); + $changes = getFormChanges( $dbStorage, $_REQUEST['newStorage'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Storage SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Storage set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( 'DELETE FROM Storage WHERE Id=?', array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Storage" ); + } + } # end if isset($_REQUEST['object'] ) + + } else if ( $action == 'version' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'ignore' : + { + dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); + break; + } + case 'hour' : + case 'day' : + case 'week' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); + break; + } + case 'never' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); + break; + } + } + } + if ( $action == 'donate' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'hour' : + case 'day' : + case 'week' : + case 'month' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); + break; + } + case 'never' : + case 'already' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); + break; + } + } // end switch option + } + if ( $action == 'options' && isset($_REQUEST['tab']) ) { + $configCat = $configCats[$_REQUEST['tab']]; + $changed = false; + foreach ( $configCat as $name=>$value ) { + unset( $newValue ); + if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) + $newValue = 0; + elseif ( isset($_REQUEST['newConfig'][$name]) ) + $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); + + if ( isset($newValue) && ($newValue != $value['Value']) ) { + dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); + $changed = true; + } + } + if ( $changed ) { + switch( $_REQUEST['tab'] ) { + case 'system' : + case 'config' : + case 'paths' : + $restartWarning = true; + break; + case 'web' : + case 'tools' : + break; + case 'logging' : + case 'network' : + case 'mail' : + case 'upload' : + $restartWarning = true; + break; + case 'highband' : + case 'medband' : + case 'lowband' : + break; + } + } + loadConfig( false ); + } elseif ( $action == 'user' ) { + if ( !empty($_REQUEST['uid']) ) + $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); + else + $dbUser = array(); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( $_REQUEST['newUser']['Password'] ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['uid']) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); + # If we are updating the logged in user, then update our session user data. + if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) + userLogin( $dbUser['Username'], $dbUser['Password'] ); + } else { + dbQuery( "insert into Users set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'state' ) { + if ( !empty($_REQUEST['runState']) ) { + //if ( $cookies ) session_write_close(); + packageControl( $_REQUEST['runState'] ); + $refreshParent = true; + } + } elseif ( $action == 'save' ) { + if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { + $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; + $definitions = array(); + foreach( dbFetchAll( $sql ) as $monitor ) + { + $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; + } + $definition = join( ',', $definitions ); + if ( $_REQUEST['newState'] ) + $_REQUEST['runState'] = $_REQUEST['newState']; + dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['runState']) ) + dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); + + if ( isset($_REQUEST['markUids']) ) { + foreach( $_REQUEST['markUids'] as $markUid ) + dbQuery( "delete from Users where Id = ?", array($markUid) ); + if ( $markUid == $user['Id'] ) + userLogout(); + } + } + } else { + if ( ZM_USER_SELF_EDIT && $action == 'user' ) { + $uid = $user['Id']; + + $dbUser = dbFetchOne( 'SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid) ); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( !empty($_REQUEST['newUser']['Password']) ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + if ( count( $changes ) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); + $refreshParent = true; + } + $view = 'none'; + } + } + + if ( $action == 'reset' ) { + $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); + setcookie( 'zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); + //if ( $cookies ) session_write_close(); + } } ?> diff --git a/web/includes/config.php.in b/web/includes/config.php.in index 801fa30cf..0af60b76a 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -24,6 +24,7 @@ define( "ZM_CONFIG", "@ZM_CONFIG@" ); // Path to config file // Define, and override any given in config file define( "ZM_VERSION", "@VERSION@" ); // Version +define( "ZM_DIR_TEMP", "@ZM_TMPDIR@" ); $configFile = ZM_CONFIG; $localConfigFile = basename($configFile); diff --git a/web/includes/csrf/LICENSE.txt b/web/includes/csrf/LICENSE.txt new file mode 100644 index 000000000..37b07717d --- /dev/null +++ b/web/includes/csrf/LICENSE.txt @@ -0,0 +1,9 @@ +Copyright (c) 2008-2013, Edward Z. Yang +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web/includes/csrf/NEWS.txt b/web/includes/csrf/NEWS.txt new file mode 100644 index 000000000..66d52f6da --- /dev/null +++ b/web/includes/csrf/NEWS.txt @@ -0,0 +1,69 @@ + + [[ news ]] + +1.0.4 released 2013-07-17 + + [SECURITY FIXES] + + - When secret key was not explicitly set, it was not being used + by the csrf_hash() function. Thanks sparticvs for reporting. + + [FEATURES] + + - The default 'CSRF check failed' page now offers a handy 'Try + again' button, which resubmits the form. + + [BUG FIXES] + + - The fix for 1.0.3 inadvertantly turned off XMLHttpRequest + overloading for all browsers; it has now been fixed to only + apply to IE. + +1.0.3 released 2012-01-31 + + [BUG FIXES] + + - Internet Explorer 8 adds support for XMLHttpRequest.prototype, + but this support is broken for method overloading. We + explicitly disable JavaScript overloading for Internet Explorer. + Thanks Kelly Lu for reporting. + + - A global declaration was omitted, resulting in a variable + not being properly introduced in PHP 5.3. Thanks Whitney Beck for + reporting. + +1.0.2 released 2009-03-08 + + [SECURITY FIXES] + + - Due to a typo, csrf-magic accidentally treated the secret key + as always present. This means that there was a possible CSRF + attack against users without any cookies. No attacks in the + wild were known at the time of this release. Thanks Jakub + Vrána for reporting. + +1.0.1 released 2008-11-02 + + [NEW FEATURES] + + - Support for composite tokens; this also fixes a bug with using + IP-based tokens for users with cookies disabled. + + - Native support cookie tokens; use csrf_conf('cookie', $name) to + specify the name of a cookie that the CSRF token should be + placed in. This is useful if you have a Squid cache, and need + to configure it to ignore this token. + + - Tips/tricks section in README.txt. + + - There is now a two hour expiration time on all tokens. This + can be modified using csrf_conf('expires', $seconds). + + - ClickJacking protection using an iframe breaker. Disable with + csrf_conf('frame-breaker', false). + + [BUG FIXES] + + - CsrfMagic.send() incorrectly submitted GET requests twice, + once without the magic token and once with the token. Reported + by Kelly Lu . diff --git a/web/includes/csrf/README.txt b/web/includes/csrf/README.txt new file mode 100644 index 000000000..98d225dba --- /dev/null +++ b/web/includes/csrf/README.txt @@ -0,0 +1,160 @@ + + [[ csrf-magic ]] + +Add the following line to the top of all web-accessible PHP pages. If you have +a common file included by everything, put it there. + + include_once '/path/to/csrf-magic.php'; + +Do it, test it, then forget about it. csrf-magic is protecting you if nothing +bad happens. Read on if you run into problems. + + + TABLE OF CONTENTS + + ------------------- + + 1. TIPS AND TRICKS + 2. AJAX + 3. CONFIGURE + 4. THANKS + 5. FOOTNOTES + + ------------------- + + + +1. TIPS AND TRICKS + + * If your JavaScript and AJAX is persistently getting errors, check the + AJAX section below on how to fix. + + * The CSS overlay protection makes it impossible to display your website + in frame/iframe elements. You can disable it with + csrf_conf('frame-breaker', false) in your csrf_startup() function. + + * csrf-magic will start a session. To disable, use csrf_conf('auto-session', + false) in your csrf_startup() function. + + * The default error message is a little user unfriendly. Write your own + function which outputs an error message and set csrf_conf('callback', + 'myCallbackFunction') in your csrf_startup() function. + + * Make sure csrf_conf('secret', 'ABCDEFG') has something random in it. If + the directory csrf-magic.php is in is writable, csrf-magic will generate + a secret key for you in the csrf-secret.php file. + + * Remember you can use auto_prepend to include csrf-magic.php on all your + pages. You may want to create a stub file which you can include that + includes csrf-magic.php as well as performs configuration. + + * The default expiration time for tokens is two hours. If you expect your + users to need longer to fill out forms, be sure to enable double + submission when the token is invalid. + + +2. AJAX + +csrf-magic has the ability to dynamically rewrite AJAX requests which use +XMLHttpRequest. However, due to the invasiveness of this procedure, it is +not enabled by default. You can enable it by adding this code before you +include csrf-magic.php. + + function csrf_startup() { + csrf_conf('rewrite-js', '/web/path/to/csrf-magic.js'); + } + // include_once '/path/to/csrf-magic.php'; + +(Be sure to place csrf-magic.js somewhere web accessible). + +The default method CSRF Magic uses to rewrite AJAX requests will +only work for browsers with support for XmlHttpRequest.prototype (this excludes +all versions of Internet Explorer). See this page for more information: +http://stackoverflow.com/questions/664315/internet-explorer-8-prototypes-and-xmlhttprequest + +However, csrf-magic.js will +automatically detect and play nice with the following JavaScript frameworks: + + * jQuery + * Prototype + * MooTools + * Ext + * Dojo + +(Note 2013-07-16: It has been a long time since this manual support has +been updated, and some JavaScript libraries have placed their copies of XHR +in local variables in closures, which makes it difficult for us to monkey-patch +it in automatically.) + +To rewrite your own JavaScript library to use csrf-magic.js, you should modify +your function that generates XMLHttpRequest to have this at the end: + + return new CsrfMagic(xhrObject); + +With whatever xhrObject may be. If you have literal instances of XMLHttpRequest +in your code, find and replace ''new XMLHttpRequest'' with ''new CsrfMagic'' +(CsrfMagic will automatically instantiate an XMLHttpRequest object in a +cross-platform manner as necessary). + +If you don't want csrf-magic monkeying around with your XMLHttpRequest object, +you can manually rewrite your AJAX code to include the variable. The important +information is stored in the global variables csrfMagicName and csrfMagicToken. +CsrfMagic.process may also be of interest, as it takes one parameter, a +querystring, and prepends the CSRF token to the value. + + +3. CONFIGURE + +csrf-magic has some configuration options that you can set inside the +csrf_startup() function. They are described in csrf-magic.php, and you can +set them using the convenience function csrf_conf($name, $value). + +For example, this is a recommended configuration: + + /** + * This is a function that gets called if a csrf check fails. csrf-magic will + * then exit afterwards. + */ + function my_csrf_callback() { + echo "You're doing bad things young man!"; + } + + function csrf_startup() { + + // While csrf-magic has a handy little heuristic for determining whether + // or not the content in the buffer is HTML or not, you should really + // give it a nudge and turn rewriting *off* when the content is + // not HTML. Implementation details will vary. + if (isset($_POST['ajax'])) csrf_conf('rewrite', false); + + // This is a secret value that must be set in order to enable username + // and IP based checks. Don't show this to anyone. A secret id will + // automatically be generated for you if the directory csrf-magic.php + // is placed in is writable. + csrf_conf('secret', 'ABCDEFG123456'); + + // This enables JavaScript rewriting and will ensure your AJAX calls + // don't stop working. + csrf_conf('rewrite-js', '/csrf-magic.js'); + + // This makes csrf-magic call my_csrf_callback() before exiting when + // there is a bad csrf token. This lets me customize the error page. + csrf_conf('callback', 'my_csrf_callback'); + + // While this is enabled by default to boost backwards compatibility, + // for security purposes it should ideally be off. Some users can be + // NATted or have dialup addresses which rotate frequently. Cookies + // are much more reliable. + csrf_conf('allow-ip', false); + + } + + // Finally, include the library + include_once '/path/to/csrf-magic.php'; + +Configuration gets stored in the $GLOBALS['csrf'] array. + + +4. THANKS + +My thanks to Chris Shiflett, for unintentionally inspiring the idea, as well +as telling me the original variant of the Bob and Mallory story, +and the Django CSRF Middleware authors, who thought up of this before me. +Gareth Heyes suggested using the frame-breaker option to protect against +CSS overlay attacks. diff --git a/web/includes/csrf/csrf-magic.js b/web/includes/csrf/csrf-magic.js new file mode 100644 index 000000000..0989c1065 --- /dev/null +++ b/web/includes/csrf/csrf-magic.js @@ -0,0 +1,191 @@ +/** + * @file + * + * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory + * plays nice with other JavaScript libraries, needs testing though. + */ + +// Here are the basic overloaded method definitions +// The wrapper must be set BEFORE onreadystatechange is written to, since +// a bug in ActiveXObject prevents us from properly testing for it. +CsrfMagic = function(real) { + // try to make it ourselves, if you didn't pass it + if (!real) try { real = new XMLHttpRequest; } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;} + this.csrf = real; + // properties + var csrfMagic = this; + real.onreadystatechange = function() { + csrfMagic._updateProps(); + return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; + }; + csrfMagic._updateProps(); +} + +CsrfMagic.prototype = { + + open: function(method, url, async, username, password) { + if (method == 'POST') this.csrf_isPost = true; + // deal with Opera bug, thanks jQuery + if (username) return this.csrf_open(method, url, async, username, password); + else return this.csrf_open(method, url, async); + }, + csrf_open: function(method, url, async, username, password) { + if (username) return this.csrf.open(method, url, async, username, password); + else return this.csrf.open(method, url, async); + }, + + send: function(data) { + if (!this.csrf_isPost) return this.csrf_send(data); + prepend = csrfMagicName + '=' + csrfMagicToken + '&'; + // XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers + // if (this.csrf_purportedLength === undefined) { + // this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length); + // delete this.csrf_purportedLength; + // } + delete this.csrf_isPost; + return this.csrf_send(prepend + data); + }, + csrf_send: function(data) { + return this.csrf.send(data); + }, + + setRequestHeader: function(header, value) { + // We have to auto-set this at the end, since we don't know how long the + // nonce is when added to the data. + if (this.csrf_isPost && header == "Content-length") { + this.csrf_purportedLength = value; + return; + } + return this.csrf_setRequestHeader(header, value); + }, + csrf_setRequestHeader: function(header, value) { + return this.csrf.setRequestHeader(header, value); + }, + + abort: function() { + return this.csrf.abort(); + }, + getAllResponseHeaders: function() { + return this.csrf.getAllResponseHeaders(); + }, + getResponseHeader: function(header) { + return this.csrf.getResponseHeader(header); + } // , +} + +// proprietary +CsrfMagic.prototype._updateProps = function() { + this.readyState = this.csrf.readyState; + if (this.readyState == 4) { + this.responseText = this.csrf.responseText; + this.responseXML = this.csrf.responseXML; + this.status = this.csrf.status; + this.statusText = this.csrf.statusText; + } +} +CsrfMagic.process = function(base) { + if(typeof base == 'object') { + base[csrfMagicName] = csrfMagicToken; + return base; + } + var prepend = csrfMagicName + '=' + csrfMagicToken; + if (base) return prepend + '&' + base; + return prepend; +} +// callback function for when everything on the page has loaded +CsrfMagic.end = function() { + // This rewrites forms AGAIN, so in case buffering didn't work this + // certainly will. + forms = document.getElementsByTagName('form'); + for (var i = 0; i < forms.length; i++) { + form = forms[i]; + if (form.method.toUpperCase() !== 'POST') continue; + if (form.elements[csrfMagicName]) continue; + var input = document.createElement('input'); + input.setAttribute('name', csrfMagicName); + input.setAttribute('value', csrfMagicToken); + input.setAttribute('type', 'hidden'); + form.appendChild(input); + } +} + +// Sets things up for Mozilla/Opera/nice browsers +// We very specifically match against Internet Explorer, since they haven't +// implemented prototypes correctly yet. +if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') { + var x = XMLHttpRequest.prototype; + var c = CsrfMagic.prototype; + + // Save the original functions + x.csrf_open = x.open; + x.csrf_send = x.send; + x.csrf_setRequestHeader = x.setRequestHeader; + + // Notice that CsrfMagic is itself an instantiatable object, but only + // open, send and setRequestHeader are necessary as decorators. + x.open = c.open; + x.send = c.send; + x.setRequestHeader = c.setRequestHeader; +} else { + // The only way we can do this is by modifying a library you have been + // using. We support YUI, script.aculo.us, prototype, MooTools, + // jQuery, Ext and Dojo. + if (window.jQuery) { + // jQuery didn't implement a new XMLHttpRequest function, so we have + // to do this the hard way. + jQuery.csrf_ajax = jQuery.ajax; + jQuery.ajax = function( s ) { + if (s.type && s.type.toUpperCase() == 'POST') { + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + if ( s.data && s.processData && typeof s.data != "string" ) { + s.data = jQuery.param(s.data); + } + s.data = CsrfMagic.process(s.data); + } + return jQuery.csrf_ajax( s ); + } + } + if (window.Prototype) { + // This works for script.aculo.us too + Ajax.csrf_getTransport = Ajax.getTransport; + Ajax.getTransport = function() { + return new CsrfMagic(Ajax.csrf_getTransport()); + } + } + if (window.MooTools) { + Browser.csrf_Request = Browser.Request; + Browser.Request = function () { + return new CsrfMagic(Browser.csrf_Request()); + } + } + if (window.YAHOO) { + // old YUI API + YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; + YAHOO.util.Connect.createXhrObject = function (transaction) { + obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.Ext) { + // Ext can use other js libraries as loaders, so it has to come last + // Ext's implementation is pretty identical to Yahoo's, but we duplicate + // it for comprehensiveness's sake. + Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; + Ext.lib.Ajax.createXhrObject = function (transaction) { + obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.dojo) { + // NOTE: this doesn't work with latest dojo + dojo.csrf__xhrObj = dojo._xhrObj; + dojo._xhrObj = function () { + return new CsrfMagic(dojo.csrf__xhrObj()); + } + } +} diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php new file mode 100644 index 000000000..153417f4e --- /dev/null +++ b/web/includes/csrf/csrf-magic.php @@ -0,0 +1,405 @@ + + */ +$GLOBALS['csrf']['input-name'] = '__csrf_magic'; + +/** + * Set this to false if your site must work inside of frame/iframe elements, + * but do so at your own risk: this configuration protects you against CSS + * overlay attacks that defeat tokens. + */ +$GLOBALS['csrf']['frame-breaker'] = true; + +/** + * Whether or not CSRF Magic should be allowed to start a new session in order + * to determine the key. + */ +$GLOBALS['csrf']['auto-session'] = true; + +/** + * Whether or not csrf-magic should produce XHTML style tags. + */ +$GLOBALS['csrf']['xhtml'] = true; + +// FUNCTIONS: + +// Don't edit this! +$GLOBALS['csrf']['version'] = '1.0.4'; + +/** + * Rewrites
on the fly to add CSRF tokens to them. This can also + * inject our JavaScript library. + */ +function csrf_ob_handler($buffer, $flags) { + // Even though the user told us to rewrite, we should do a quick heuristic + // to check if the page is *actually* HTML. We don't begin rewriting until + // we hit the first "; + $buffer = preg_replace('#(]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer); + if ($GLOBALS['csrf']['frame-breaker']) { + $buffer = str_ireplace('', '', $buffer); + } + if ($js = $GLOBALS['csrf']['rewrite-js']) { + $buffer = str_ireplace( + '', + ''. + '', + $buffer + ); + $script = ''; + $buffer = str_ireplace('', $script . '', $buffer, $count); + if (!$count) { + $buffer .= $script; + } + } + return $buffer; +} + +/** + * Checks if this is a post request, and if it is, checks if the nonce is valid. + * @param bool $fatal Whether or not to fatally error out if there is a problem. + * @return True if check passes or is not necessary, false if failure. + */ +function csrf_check($fatal = true) { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; + csrf_start(); + $name = $GLOBALS['csrf']['input-name']; + $ok = false; + $tokens = ''; + do { + if (!isset($_POST[$name])) break; + // we don't regenerate a token and check it because some token creation + // schemes are volatile. + $tokens = $_POST[$name]; + if (!csrf_check_tokens($tokens)) break; + $ok = true; + } while (false); + if ($fatal && !$ok) { + $callback = $GLOBALS['csrf']['callback']; + if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; + $callback($tokens); + exit; + } + return $ok; +} + +/** + * Retrieves a valid token(s) for a particular context. Tokens are separated + * by semicolons. + */ +function csrf_get_tokens() { + $has_cookies = !empty($_COOKIE); + + // $ip implements a composite key, which is sent if the user hasn't sent + // any cookies. It may or may not be used, depending on whether or not + // the cookies "stick" + $secret = csrf_get_secret(); + if (!$has_cookies && $secret) { + // :TODO: Harden this against proxy-spoofing attacks + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + $ip = ';ip:' . csrf_hash($IP_ADDRESS); + } else { + $ip = ''; + } + csrf_start(); + + // These are "strong" algorithms that don't require per se a secret + if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip; + if ($GLOBALS['csrf']['cookie']) { + $val = csrf_generate_secret(); + setcookie($GLOBALS['csrf']['cookie'], $val); + return 'cookie:' . csrf_hash($val) . $ip; + } + if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip; + // These further algorithms require a server-side secret + if (!$secret) return 'invalid'; + if ($GLOBALS['csrf']['user'] !== false) { + return 'user:' . csrf_hash($GLOBALS['csrf']['user']); + } + if ($GLOBALS['csrf']['allow-ip']) { + return ltrim($ip, ';'); + } + return 'invalid'; +} + +function csrf_flattenpost($data) { + $ret = array(); + foreach($data as $n => $v) { + $ret = array_merge($ret, csrf_flattenpost2(1, $n, $v)); + } + return $ret; +} +function csrf_flattenpost2($level, $key, $data) { + if(!is_array($data)) return array($key => $data); + $ret = array(); + foreach($data as $n => $v) { + $nk = $level >= 1 ? $key."[$n]" : "[$n]"; + $ret = array_merge($ret, csrf_flattenpost2($level+1, $nk, $v)); + } + return $ret; +} + +/** + * @param $tokens is safe for HTML consumption + */ +function csrf_callback($tokens) { + // (yes, $tokens is safe to echo without escaping) + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); + $data = ''; + foreach (csrf_flattenpost($_POST) as $key => $value) { + if ($key == $GLOBALS['csrf']['input-name']) continue; + $data .= ''; + } + echo "CSRF check failed + +

CSRF check failed. Your form session may have expired, or you may not have + cookies enabled.

+ $data +

Debug: $tokens

+"; +} + +/** + * Checks if a composite token is valid. Outward facing code should use this + * instead of csrf_check_token() + */ +function csrf_check_tokens($tokens) { + if (is_string($tokens)) $tokens = explode(';', $tokens); + foreach ($tokens as $token) { + if (csrf_check_token($token)) return true; + } + return false; +} + +/** + * Checks if a token is valid. + */ +function csrf_check_token($token) { + if (strpos($token, ':') === false) return false; + list($type, $value) = explode(':', $token, 2); + if (strpos($value, ',') === false) return false; + list($x, $time) = explode(',', $token, 2); + if ($GLOBALS['csrf']['expires']) { + if (time() > $time + $GLOBALS['csrf']['expires']) return false; + } + switch ($type) { + case 'sid': + return $value === csrf_hash(session_id(), $time); + case 'cookie': + $n = $GLOBALS['csrf']['cookie']; + if (!$n) return false; + if (!isset($_COOKIE[$n])) return false; + return $value === csrf_hash($_COOKIE[$n], $time); + case 'key': + if (!$GLOBALS['csrf']['key']) return false; + return $value === csrf_hash($GLOBALS['csrf']['key'], $time); + // We could disable these 'weaker' checks if 'key' was set, but + // that doesn't make me feel good then about the cookie-based + // implementation. + case 'user': + if (!csrf_get_secret()) return false; + if ($GLOBALS['csrf']['user'] === false) return false; + return $value === csrf_hash($GLOBALS['csrf']['user'], $time); + case 'ip': + if (!csrf_get_secret()) return false; + // do not allow IP-based checks if the username is set, or if + // the browser sent cookies + if ($GLOBALS['csrf']['user'] !== false) return false; + if (!empty($_COOKIE)) return false; + if (!$GLOBALS['csrf']['allow-ip']) return false; + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + return $value === csrf_hash($IP_ADDRESS, $time); + } + return false; +} + +/** + * Sets a configuration value. + */ +function csrf_conf($key, $val) { + if (!isset($GLOBALS['csrf'][$key])) { + trigger_error('No such configuration ' . $key, E_USER_WARNING); + return; + } + $GLOBALS['csrf'][$key] = $val; +} + +/** + * Starts a session if we're allowed to. + */ +function csrf_start() { + if ($GLOBALS['csrf']['auto-session'] && !session_id()) { + session_start(); + } +} + +/** + * Retrieves the secret, and generates one if necessary. + */ +function csrf_get_secret() { + if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret']; + $dir = dirname(__FILE__); + $file = $dir . '/csrf-secret.php'; + $secret = ''; + if (file_exists($file)) { + include $file; + return $secret; + } + if (is_writable($dir)) { + $secret = csrf_generate_secret(); + $fh = fopen($file, 'w'); + fwrite($fh, 'setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } catch(PDOException $ex ) { - echo "Unable to connect to ZM db." . $ex->getMessage(); - $dbConn = null; - } + try { + $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS ); + $dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + $dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch(PDOException $ex ) { + echo "Unable to connect to ZM db." . $ex->getMessage(); + $dbConn = null; + } } dbConnect(); -function dbDisconnect() -{ - global $dbConn; - $dbConn = null; +function dbDisconnect() { + global $dbConn; + $dbConn = null; } -function dbLogOff() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_OFF; +function dbLogOff() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_OFF; } -function dbLogOn() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_ONLY; +function dbLogOn() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_ONLY; } -function dbLogDebug() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_DEBUG; +function dbLogDebug() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_DEBUG; } -function dbDebug() -{ - dbLogDebug(); +function dbDebug() { + dbLogDebug(); } -function dbLog( $sql, $update=false ) -{ - global $dbLogLevel; - $noExecute = $update && ($dbLogLevel >= DB_LOG_DEBUG); - if ( $dbLogLevel > DB_LOG_OFF ) - Debug( "SQL-LOG: $sql".($noExecute?" (not executed)":"") ); - return( $noExecute ); +function dbLog( $sql, $update=false ) { + global $dbLogLevel; + $noExecute = $update && ($dbLogLevel >= DB_LOG_DEBUG); + if ( $dbLogLevel > DB_LOG_OFF ) + Logger::Debug( "SQL-LOG: $sql".($noExecute?" (not executed)":"") ); + return( $noExecute ); } -function dbError( $sql ) -{ - Fatal( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); +function dbError( $sql ) { + Fatal( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); } -function dbEscape( $string ) -{ - global $dbConn; - if ( version_compare( phpversion(), "4.3.0", "<") ) - if ( get_magic_quotes_gpc() ) - return( $dbConn->quote( stripslashes( $string ) ) ); - else - return( $dbConn->quote( $string ) ); +function dbEscape( $string ) { + global $dbConn; + if ( version_compare( phpversion(), "4.3.0", "<") ) + if ( get_magic_quotes_gpc() ) + return( $dbConn->quote( stripslashes( $string ) ) ); else - if ( get_magic_quotes_gpc() ) - return( $dbConn->quote( stripslashes( $string ) ) ); - else - return( $dbConn->quote( $string ) ); + return( $dbConn->quote( $string ) ); + else + if ( get_magic_quotes_gpc() ) + return( $dbConn->quote( stripslashes( $string ) ) ); + else + return( $dbConn->quote( $string ) ); } function dbQuery( $sql, $params=NULL ) { - global $dbConn; - if ( dbLog( $sql, true ) ) - return; - $result = NULL; - try { - if ( isset($params) ) { - $result = $dbConn->prepare( $sql ); - $result->execute( $params ); - } else { - $result = $dbConn->query( $sql ); - } - } catch(PDOException $e) { - Fatal( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."'" ); + global $dbConn; + if ( dbLog( $sql, true ) ) + return; + $result = NULL; + try { + if ( isset($params) ) { + $result = $dbConn->prepare( $sql ); + $result->execute( $params ); + } else { + $result = $dbConn->query( $sql ); } - return( $result ); + } catch(PDOException $e) { + Fatal( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."'" ); + } + return( $result ); } -function dbFetchOne( $sql, $col=false, $params=NULL ) -{ - $result = dbQuery( $sql, $params ); - if ( ! $result ) { - Fatal( "SQL-ERR dbFetchOne no result, statement was '".$sql."'" . ( $params ? 'params: ' . join(',',$params) : '' ) ); - return false; - } +function dbFetchOne( $sql, $col=false, $params=NULL ) { + $result = dbQuery( $sql, $params ); + if ( ! $result ) { + Fatal( "SQL-ERR dbFetchOne no result, statement was '".$sql."'" . ( $params ? 'params: ' . join(',',$params) : '' ) ); + return false; + } - if ( $result && $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - return( $col?$dbRow[$col]:$dbRow ); - return( false ); + if ( $result && $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) { + if ( $col ) { + if ( ! isset( $dbRow[$col] ) ) { + Warning( "$col does not exist in the returned row" ); + } + return $dbRow[$col]; + } + return $dbRow; + } + return( false ); } -function dbFetchAll( $sql, $col=false, $params=NULL ) -{ - $result = dbQuery( $sql, $params ); - if ( ! $result ) { - Fatal( "SQL-ERR dbFetchAll no result, statement was '".$sql."'" . ( $params ? 'params: ' .join(',', $params) : '' ) ); - return false; - } +function dbFetchAll( $sql, $col=false, $params=NULL ) { + $result = dbQuery( $sql, $params ); + if ( ! $result ) { + Fatal( "SQL-ERR dbFetchAll no result, statement was '".$sql."'" . ( $params ? 'params: ' .join(',', $params) : '' ) ); + return false; + } - $dbRows = array(); - while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - $dbRows[] = $col?$dbRow[$col]:$dbRow; - return( $dbRows ); + $dbRows = array(); + while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + $dbRows[] = $col?$dbRow[$col]:$dbRow; + return( $dbRows ); } -function dbFetchAssoc( $sql, $indexCol, $dataCol=false ) -{ - $result = dbQuery( $sql ); +function dbFetchAssoc( $sql, $indexCol, $dataCol=false ) { + $result = dbQuery( $sql ); - $dbRows = array(); - while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - $dbRows[$dbRow[$indexCol]] = $dataCol?$dbRow[$dataCol]:$dbRow; - return( $dbRows ); + $dbRows = array(); + while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + $dbRows[$dbRow[$indexCol]] = $dataCol?$dbRow[$dataCol]:$dbRow; + return( $dbRows ); } -function dbFetch( $sql, $col=false ) -{ - return( dbFetchAll( $sql, $col ) ); +function dbFetch( $sql, $col=false ) { + return( dbFetchAll( $sql, $col ) ); } -function dbFetchNext( $result, $col=false ) -{ - if ( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - return( $col?$dbRow[$col]:$dbRow ); - return( false ); +function dbFetchNext( $result, $col=false ) { + if ( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + return( $col?$dbRow[$col]:$dbRow ); + return( false ); } -function dbNumRows( $sql ) -{ - $result = dbQuery( $sql ); - return( $result->rowCount() ); +function dbNumRows( $sql ) { + $result = dbQuery( $sql ); + return( $result->rowCount() ); } -function dbInsertId() -{ - global $dbConn; - return( $dbConn->lastInsertId() ); +function dbInsertId() { + global $dbConn; + return( $dbConn->lastInsertId() ); } -function getEnumValues( $table, $column ) -{ - $row = dbFetchOne( "describe $table $column" ); - preg_match_all( "/'([^']+)'/", $row['Type'], $matches ); - return( $matches[1] ); +function getEnumValues( $table, $column ) { + $row = dbFetchOne( "describe $table $column" ); + preg_match_all( "/'([^']+)'/", $row['Type'], $matches ); + return( $matches[1] ); } -function getSetValues( $table, $column ) -{ - return( getEnumValues( $table, $column ) ); +function getSetValues( $table, $column ) { + return( getEnumValues( $table, $column ) ); } -function getUniqueValues( $table, $column, $asString=1 ) -{ - $values = array(); - $sql = "select distinct $column from $table where (not isnull($column) and $column != '') order by $column"; - foreach( dbFetchAll( $sql ) as $row ) - { - if ( $asString ) - $values[$row[$column]] = $row[$column]; - else - $values[] = $row[$column]; - } - return( $values ); +function getUniqueValues( $table, $column, $asString=1 ) { + $values = array(); + $sql = "select distinct $column from $table where (not isnull($column) and $column != '') order by $column"; + foreach( dbFetchAll( $sql ) as $row ) { + if ( $asString ) + $values[$row[$column]] = $row[$column]; + else + $values[] = $row[$column]; + } + return( $values ); } -function getTableColumns( $table, $asString=1 ) -{ - $columns = array(); - $sql = "describe $table"; - foreach( dbFetchAll( $sql ) as $row ) - { - if ( $asString ) - $columns[$row['Field']] = $row['Type']; - else - $columns[] = $row['Type']; - } - return( $columns ); +function getTableColumns( $table, $asString=1 ) { + $columns = array(); + $sql = "describe $table"; + foreach( dbFetchAll( $sql ) as $row ) { + if ( $asString ) + $columns[$row['Field']] = $row['Type']; + else + $columns[] = $row['Type']; + } + return( $columns ); } -function getTableAutoInc( $table ) -{ - $row = dbFetchOne( "show table status where Name=?", NULL, array($table) ); - return( $row['Auto_increment'] ); +function getTableAutoInc( $table ) { + $row = dbFetchOne( "show table status where Name=?", NULL, array($table) ); + return( $row['Auto_increment'] ); } -function getTableDescription( $table, $asString=1 ) -{ - $columns = array(); - foreach( dbFetchAll( "describe $table" ) as $row ) - { - $desc = array( - 'name' => $row['Field'], - 'required' => ($row['Null']=='NO')?true:false, - 'default' => $row['Default'], - 'db' => $row, +function getTableDescription( $table, $asString=1 ) { + $columns = array(); + foreach( dbFetchAll( "describe $table" ) as $row ) { + $desc = array( + 'name' => $row['Field'], + 'required' => ($row['Null']=='NO')?true:false, + 'default' => $row['Default'], + 'db' => $row, ); - if ( preg_match( "/^varchar\((\d+)\)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - $desc['typeAttrib'] = 'varchar'; - $desc['maxLength'] = $matches[1]; - } - elseif ( preg_match( "/^(\w+)?text$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - if (!empty($matches[1]) ) - $desc['typeAttrib'] = $matches[1]; - switch ( $matches[1] ) - { - case 'tiny' : - $desc['maxLength'] = 255; - break; - case 'medium' : - $desc['maxLength'] = 32768; - break; - case '' : - case 'big' : - //$desc['minLength'] = -128; - break; - default : - Error( "Unexpected text qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); - break; - } - } - elseif ( preg_match( "/^(enum|set)\((.*)\)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - $desc['typeAttrib'] = $matches[1]; - preg_match_all( "/'([^']+)'/", $matches[2], $matches ); - $desc['values'] = $matches[1]; - } - elseif ( preg_match( "/^(\w+)?int\(\d+\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'integer'; - switch ( $matches[1] ) - { - case 'tiny' : - $desc['minValue'] = -128; - $desc['maxValue'] = 127; - break; - case 'small' : - $desc['minValue'] = -32768; - $desc['maxValue'] = 32767; - break; - case 'medium' : - $desc['minValue'] = -8388608; - $desc['maxValue'] = 8388607; - break; - case '' : - $desc['minValue'] = -2147483648; - $desc['maxValue'] = 2147483647; - break; - case 'big' : - //$desc['minValue'] = -128; - //$desc['maxValue'] = 127; - break; - default : - Error( "Unexpected integer qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); - break; - } - if ( !empty($matches[1]) ) - $desc['typeAttrib'] = $matches[1]; - if ( $desc['unsigned'] = ( isset($matches[2]) && $matches[2] == 'unsigned' ) ) - { - $desc['maxValue'] += (-$desc['minValue']); - $desc['minValue'] = 0; - } - } - elseif ( preg_match( "/^(?:decimal|numeric)\((\d+)(?:,(\d+))?\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'fixed'; - $desc['range'] = $matches[1]; - if ( isset($matches[2]) ) - $desc['precision'] = $matches[2]; - else - $desc['precision'] = 0; - $desc['unsigned'] = ( isset($matches[3]) && $matches[3] == 'unsigned' ); - } - elseif ( preg_match( "/^(datetime|timestamp|date|time)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'datetime'; - switch ( $desc['typeAttrib'] = $matches[1] ) - { - case 'datetime' : - case 'timestamp' : - $desc['hasDate'] = true; - $desc['hasTime'] = true; - break; - case 'date' : - $desc['hasDate'] = true; - $desc['hasTime'] = false; - break; - case 'time' : - $desc['hasDate'] = false; - $desc['hasTime'] = true; - break; - } - } - else - { - Error( "Can't parse database type '".$row['Type']."' found for field '".$row['Field']."' in table '".$table."'" ); - } - - if ( $asString ) - $columns[$row['Field']] = $desc; - else - $columns[] = $desc; + if ( preg_match( "/^varchar\((\d+)\)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + $desc['typeAttrib'] = 'varchar'; + $desc['maxLength'] = $matches[1]; + } elseif ( preg_match( "/^(\w+)?text$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + if (!empty($matches[1]) ) + $desc['typeAttrib'] = $matches[1]; + switch ( $matches[1] ) { + case 'tiny' : + $desc['maxLength'] = 255; + break; + case 'medium' : + $desc['maxLength'] = 32768; + break; + case '' : + case 'big' : + //$desc['minLength'] = -128; + break; + default : + Error( "Unexpected text qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); + break; + } + } elseif ( preg_match( "/^(enum|set)\((.*)\)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + $desc['typeAttrib'] = $matches[1]; + preg_match_all( "/'([^']+)'/", $matches[2], $matches ); + $desc['values'] = $matches[1]; + } elseif ( preg_match( "/^(\w+)?int\(\d+\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { + $desc['type'] = 'integer'; + switch ( $matches[1] ) { + case 'tiny' : + $desc['minValue'] = -128; + $desc['maxValue'] = 127; + break; + case 'small' : + $desc['minValue'] = -32768; + $desc['maxValue'] = 32767; + break; + case 'medium' : + $desc['minValue'] = -8388608; + $desc['maxValue'] = 8388607; + break; + case '' : + $desc['minValue'] = -2147483648; + $desc['maxValue'] = 2147483647; + break; + case 'big' : + //$desc['minValue'] = -128; + //$desc['maxValue'] = 127; + break; + default : + Error( "Unexpected integer qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); + break; + } + if ( !empty($matches[1]) ) + $desc['typeAttrib'] = $matches[1]; + if ( $desc['unsigned'] = ( isset($matches[2]) && $matches[2] == 'unsigned' ) ) { + $desc['maxValue'] += (-$desc['minValue']); + $desc['minValue'] = 0; + } + } elseif ( preg_match( "/^(?:decimal|numeric)\((\d+)(?:,(\d+))?\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { + $desc['type'] = 'fixed'; + $desc['range'] = $matches[1]; + if ( isset($matches[2]) ) + $desc['precision'] = $matches[2]; + else + $desc['precision'] = 0; + $desc['unsigned'] = ( isset($matches[3]) && $matches[3] == 'unsigned' ); + } elseif ( preg_match( "/^(datetime|timestamp|date|time)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'datetime'; + switch ( $desc['typeAttrib'] = $matches[1] ) { + case 'datetime' : + case 'timestamp' : + $desc['hasDate'] = true; + $desc['hasTime'] = true; + break; + case 'date' : + $desc['hasDate'] = true; + $desc['hasTime'] = false; + break; + case 'time' : + $desc['hasDate'] = false; + $desc['hasTime'] = true; + break; + } + } else { + Error( "Can't parse database type '".$row['Type']."' found for field '".$row['Field']."' in table '".$table."'" ); } - return( $columns ); + + if ( $asString ) + $columns[$row['Field']] = $desc; + else + $columns[] = $desc; + } + return( $columns ); } -function dbFetchMonitor( $mid ) -{ - return( dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ) ); +function dbFetchMonitor( $mid ) { + return( dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ) ); } -function dbFetchGroup( $gid ) -{ - return( dbFetchOne( "select * from Groups where Id = ?", NULL, array($gid) ) ); +function dbFetchGroup( $gid ) { + return( dbFetchOne( "select * from Groups where Id = ?", NULL, array($gid) ) ); } ?> diff --git a/web/includes/functions.php b/web/includes/functions.php index bd1375b73..b76cb5c55 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -19,7 +19,7 @@ // // Compatibility functions -if ( version_compare( phpversion(), "4.3.0", "<") ) { +if ( version_compare( phpversion(), '4.3.0', '<') ) { function ob_get_clean() { $buffer = ob_get_contents(); ob_end_clean(); @@ -27,24 +27,28 @@ if ( version_compare( phpversion(), "4.3.0", "<") ) { } } -function userLogin( $username, $password="", $passwordHashed=false ) { +# We are requiring these because this file is getting included from the api, which hasn't already included them. +require_once( 'logger.php' ); +require_once( 'database.php' ); + +function userLogin( $username, $password='', $passwordHashed=false ) { global $user, $cookies; - $sql = "select * from Users where Enabled = 1"; + $sql = 'SELECT * FROM Users WHERE Enabled = 1'; $sql_values = NULL; - if ( ZM_AUTH_TYPE == "builtin" ) { + if ( ZM_AUTH_TYPE == 'builtin' ) { if ( $passwordHashed ) { - $sql .= " AND Username=? AND Password=?"; + $sql .= ' AND Username=? AND Password=?'; } else { - $sql .= " AND Username=? AND Password=password(?)"; + $sql .= ' AND Username=? AND Password=password(?)'; } $sql_values = array( $username, $password ); } else { - $sql .= " AND Username = ?"; + $sql .= ' AND Username = ?'; $sql_values = array( $username ); } $_SESSION['username'] = $username; - if ( ZM_AUTH_RELAY == "plain" ) { + if ( ZM_AUTH_RELAY == 'plain' ) { // Need to save this in session $_SESSION['password'] = $password; } @@ -53,9 +57,10 @@ function userLogin( $username, $password="", $passwordHashed=false ) { Info( "Login successful for user \"$username\"" ); $_SESSION['user'] = $user = $dbUser; unset($_SESSION['loginFailed']); - if ( ZM_AUTH_TYPE == "builtin" ) { + if ( ZM_AUTH_TYPE == 'builtin' ) { $_SESSION['passwordHash'] = $user['Password']; } + session_regenerate_id(); } else { Warning( "Login denied for user \"$username\"" ); $_SESSION['loginFailed'] = true; @@ -78,11 +83,11 @@ function userLogout() { } function noCacheHeaders() { - header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past - header("Last-Modified: ".gmdate( "D, d M Y H:i:s" )." GMT"); // always modified - header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 - header("Cache-Control: post-check=0, pre-check=0", false); - header("Pragma: no-cache"); // HTTP/1.0 + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past + header('Last-Modified: '.gmdate( 'D, d M Y H:i:s' ).' GMT'); // always modified + header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 + header('Cache-Control: post-check=0, pre-check=0', false); + header('Pragma: no-cache'); // HTTP/1.0 } function CORSHeaders() { @@ -99,28 +104,28 @@ function CORSHeaders() { $Server = new Server( $row ); if ( $_SERVER['HTTP_ORIGIN'] == $Server->Url() ) { $valid = true; - header("Access-Control-Allow-Origin: " . $Server->Url() ); - header("Access-Control-Allow-Headers: x-requested-with,x-request"); + header('Access-Control-Allow-Origin: ' . $Server->Url() ); + header('Access-Control-Allow-Headers: x-requested-with,x-request'); } } if ( ! $valid ) { - Warning( $_SERVER['HTTP_ORIGIN'] . " is not found in servers list." ); + Warning( $_SERVER['HTTP_ORIGIN'] . ' is not found in servers list.' ); } } } function getAuthUser( $auth ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" && !empty($auth) ) { - $remoteAddr = ""; + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) { + $remoteAddr = ''; if ( ZM_AUTH_HASH_IPS ) { $remoteAddr = $_SERVER['REMOTE_ADDR']; if ( !$remoteAddr ) { Error( "Can't determine remote address for authentication, using empty string" ); - $remoteAddr = ""; + $remoteAddr = ''; } } - $sql = "select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1"; + $sql = 'select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1'; foreach ( dbFetchAll( $sql ) as $user ) { $now = time(); for ( $i = 0; $i < 2; $i++, $now -= (60*60) ) { // Try for last two hours @@ -139,7 +144,7 @@ function getAuthUser( $auth ) { } function generateAuthHash( $useRemoteAddr ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { $time = localtime(); if ( $useRemoteAddr ) { $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$_SESSION['remoteAddr'].$time[2].$time[3].$time[4].$time[5]; @@ -148,7 +153,7 @@ function generateAuthHash( $useRemoteAddr ) { } $auth = md5( $authKey ); } else { - $auth = ""; + $auth = ''; } return( $auth ); } @@ -157,24 +162,24 @@ function getStreamSrc( $args, $querySep='&' ) { $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args[] = 'auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args[] = 'user='.$_SESSION['username']; + $args[] = 'pass='.$_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $args[] = 'user='.$_SESSION['username']; } } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; + if ( !in_array( 'mode=single', $args ) && !empty($GLOBALS['connkey']) ) { + $args[] = 'connkey='.$GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); + $args[] = 'rand='.time(); } if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); + $streamSrc .= '?'.join( $querySep, $args ); } return( $streamSrc ); @@ -192,11 +197,11 @@ function getMimeType( $file ) { return( trim( exec( 'file -bi '.escapeshellarg( $file ).' 2>/dev/null' ) ) ); } -function outputVideoStream( $id, $src, $width, $height, $format, $title="" ) { +function outputVideoStream( $id, $src, $width, $height, $format, $title='' ) { echo getVideoStreamHTML( $id, $src, $width, $height, $format, $title ); } -function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { +function getVideoStreamHTML( $id, $src, $width, $height, $format, $title='' ) { $html = ''; $width = validInt($width); $height = validInt($height); @@ -207,24 +212,24 @@ function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { } else { switch( $format ) { case 'asf' : - $mimeType = "video/x-ms-asf"; + $mimeType = 'video/x-ms-asf'; break; case 'avi' : case 'wmv' : - $mimeType = "video/x-msvideo"; + $mimeType = 'video/x-msvideo'; break; case 'mov' : - $mimeType = "video/quicktime"; + $mimeType = 'video/quicktime'; break; case 'mpg' : case 'mpeg' : - $mimeType = "video/mpeg"; + $mimeType = 'video/mpeg'; break; case 'swf' : - $mimeType = "application/x-shockwave-flash"; + $mimeType = 'application/x-shockwave-flash'; break; case '3gp' : - $mimeType = "video/3gpp"; + $mimeType = 'video/3gpp'; break; default : $mimeType = "video/$format"; @@ -235,9 +240,9 @@ function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { $mimeType = 'video/'.$format; if ( ZM_WEB_USE_OBJECT_TAGS ) { switch( $mimeType ) { - case "video/x-ms-asf" : - case "video/x-msvideo" : - case "video/mp4" : + case 'video/x-ms-asf' : + case 'video/x-msvideo' : + case 'video/mp4' : { if ( isWindows() ) { return ''; } } - case "video/quicktime" : + case 'video/quicktime' : { return ' '; } - case "application/x-shockwave-flash" : + case 'application/x-shockwave-flash' : { return ''; } -function outputImageStream( $id, $src, $width, $height, $title="" ) { +function outputImageStream( $id, $src, $width, $height, $title='' ) { echo getImageStream( $id, $src, $width, $height, $title ); } -function getImageStream( $id, $src, $width, $height, $title="" ) { +function getImageStream( $id, $src, $width, $height, $title='' ) { if ( canStreamIframe() ) { - return '