Merge branch 'master' into fmt

This commit is contained in:
Isaac Connor 2022-02-26 13:56:20 -05:00 committed by GitHub
commit cad57df0bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
214 changed files with 7116 additions and 3095 deletions

View File

@ -5,6 +5,7 @@ web/api/lib
web/includes/csrf/
web/js/videojs.zoomrotate.js
web/skins/classic/js/bootstrap-4.5.0.js
web/skins/classic/js/bootstrap.bundle.min.js
web/skins/classic/js/chosen
web/skins/classic/js/dateTimePicker
web/skins/classic/js/jquery-*.js
@ -13,6 +14,8 @@ web/skins/classic/js/jquery.js
web/skins/classic/js/moment.js
web/skins/classic/js/video.js
web/tools/mootools
web/js/janus.js
web/js/ajaxQueue.js
# Cannot be parsed as JS
web/skins/classic/includes/export_functions.php

View File

@ -3,7 +3,7 @@
module.exports = {
"env": {
"browser": true,
"es6": true,
"es2017": true,
},
"extends": ["google"],
"overrides": [{

View File

@ -19,7 +19,7 @@ jobs:
- crypto_backend: gnutls
jwt_backend: libjwt
runs-on: ubuntu-latest
container: centos:8
container: rockylinux:8
steps:
- name: Enable RPMFusion, EPEL and PowerTools

View File

@ -16,6 +16,6 @@ jobs:
with:
submodules: recursive
- name: Install ESLint
run: npm install eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5
run: npm install eslint@8.7.0 eslint-config-google@0.14.0 eslint-plugin-html@6.2.0 eslint-plugin-php-markup@6.0.0
- name: Run ESLint
run: npx eslint --ext .php,.js .

View File

@ -2,8 +2,7 @@ name: Create packages
on:
push:
branches:
- '*'
branches: [ master ]
pull_request:
branches: [ master ]
@ -21,6 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
fetch-depth: '0'
submodules: recursive
- name: Run packpack
env:
@ -29,3 +29,13 @@ jobs:
DIST: ${{ matrix.os_dist.dist }}
DOCKER_REPO: iconzm/packpack
run: utils/packpack/startpackpack.sh
- name: Publish
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.ZMREPO_SSH_KEY }}
ARGS: "-rltgoDzvO"
SOURCE: build/
REMOTE_HOST: ${{ secrets.ZMREPO_HOST }}
REMOTE_USER: ${{ secrets.ZMREPO_SSH_USER }}
TARGET: debian/master/mini-dinstall/incoming/

9
.readthedocs.yaml Normal file
View File

@ -0,0 +1,9 @@
version: 2
build:
os: "ubuntu-20.04"
tools:
python: "3.8"
sphinx:
fail_on_warning: true

View File

@ -83,6 +83,7 @@ mark_as_advanced(
ZM_TARGET_DISTRO
ZM_PATH_MAP
ZM_PATH_ARP
ZM_PATH_ARP_SCAN
ZM_CONFIG_DIR
ZM_CONFIG_SUBDIR
ZM_SYSTEMD
@ -145,6 +146,8 @@ set(ZM_PATH_MAP "/dev/shm" CACHE PATH
"Location to save mapped memory files, default: /dev/shm")
set(ZM_PATH_ARP "" CACHE PATH
"Full path to compatible arp binary. Leave empty for automatic detection.")
set(ZM_PATH_ARP_SCAN "" CACHE PATH
"Full path to compatible scan_arp binary. Leave empty for automatic detection.")
set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH
"Location of ZoneMinder configuration, default system config directory")
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH
@ -167,6 +170,8 @@ set(ZM_NO_X10 "OFF" CACHE BOOL
set(ZM_ONVIF "ON" CACHE BOOL
"Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not
work with all cameras claiming to be ONVIF compliant. default: ON")
set(ZM_NO_PCRE "OFF" CACHE BOOL
"Set to ON to skip libpcre3 checks and force building ZM without libpcre3. default: OFF")
set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL
"Set to ON to skip building ZM with rtsp server support. default: OFF")
set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING
@ -316,10 +321,10 @@ endif()
# Do not check for cURL if ZM_NO_CURL is on
if(NOT ZM_NO_CURL)
# cURL
find_package(CURL)
find_package(CURL REQUIRED)
if(CURL_FOUND)
set(HAVE_LIBCURL 1)
#list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES})
list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES})
include_directories(${CURL_INCLUDE_DIRS})
set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS})
check_include_file("curl/curl.h" HAVE_CURL_CURL_H)
@ -407,21 +412,24 @@ else()
message(FATAL_ERROR "ZoneMinder requires pthread but it was not found on your system")
endif()
# pcre (using find_library and find_path)
find_library(PCRE_LIBRARIES pcre)
if(PCRE_LIBRARIES)
set(HAVE_LIBPCRE 1)
list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}")
find_path(PCRE_INCLUDE_DIR pcre.h)
if(PCRE_INCLUDE_DIR)
include_directories("${PCRE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}")
# Do not check for cURL if ZM_NO_CURL is on
if(NOT ZM_NO_PRCE)
# pcre (using find_library and find_path)
find_library(PCRE_LIBRARIES pcre)
if(PCRE_LIBRARIES)
set(HAVE_LIBPCRE 1)
list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}")
find_path(PCRE_INCLUDE_DIR pcre.h)
if(PCRE_INCLUDE_DIR)
include_directories("${PCRE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}")
endif()
mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR)
check_include_file("pcre.h" HAVE_PCRE_H)
set(optlibsfound "${optlibsfound} PCRE")
else()
set(optlibsnotfound "${optlibsnotfound} PCRE")
endif()
mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR)
check_include_file("pcre.h" HAVE_PCRE_H)
set(optlibsfound "${optlibsfound} PCRE")
else()
set(optlibsnotfound "${optlibsnotfound} PCRE")
endif()
# mysqlclient (using find_library and find_path)
@ -513,6 +521,15 @@ endif()
#list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}")
#endif()
find_package(GSOAP 2.0.0)
if (GSOAP_FOUND)
set(optlibsfound "${optlibsfound} gsoap")
add_compile_definitions(WITH_GSOAP)
else()
set(optlibsnotfound "${optlibsnotfound} gsoap")
endif()
if(NOT ZM_NO_RTSPSERVER)
set(HAVE_RTSP_SERVER 1)
else()
@ -542,6 +559,7 @@ set(ZM_PCRE 0)
if(HAVE_LIBPCRE AND HAVE_PCRE_H)
set(ZM_PCRE 1)
endif()
# Check for mmap and enable in all components
set(ZM_MEM_MAPPED 0)
set(ENABLE_MMAP no)
@ -628,6 +646,18 @@ if(ZM_PATH_ARP STREQUAL "")
endif()
endif()
# Find the path to an arp-scan compatible executable
if(ZM_PATH_ARP_SCAN STREQUAL "")
find_program(ARP_SCAN_EXECUTABLE arp-scan)
if(ARP_SCAN_EXECUTABLE)
set(ZM_PATH_ARP_SCAN "${ARP_SCAN_EXECUTABLE}")
mark_as_advanced(ARP_SCAN_EXECUTABLE)
endif()
if(ARP_SCAN_EXECUTABLE-NOTFOUND)
message(WARNING "Unable to find a compatible arp-scan binary. Monitor probe will be less powerful.")
endif()
endif()
# Some variables that zm expects
set(ZM_PID "${ZM_RUNDIR}/zm.pid")
set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf")

View File

@ -1,10 +1,8 @@
ZoneMinder
==========
[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder)
[![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE)
[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder)
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
@ -26,7 +24,7 @@ https://github.com/ZoneMinder/zmdockerfiles
This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros:
- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor)
- Ubuntu via [Isaac Connor's PPA](https://launchpad.net/~iconnor)
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
- RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org)
- Fedora via [RPM Fusion](http://rpmfusion.org)

100
cmake/Modules/FindFmt.cmake Normal file
View File

@ -0,0 +1,100 @@
# FindFmt
# -------
# Finds the Fmt library
#
# This will define the following variables::
#
# FMT_FOUND - system has Fmt
# FMT_INCLUDE_DIRS - the Fmt include directory
# FMT_LIBRARIES - the Fmt libraries
#
# and the following imported targets::
#
# Fmt::Fmt - The Fmt library
if(ENABLE_INTERNAL_FMT)
include(ExternalProject)
file(STRINGS ${CMAKE_SOURCE_DIR}/tools/depends/target/libfmt/Makefile VER REGEX "^[ ]*VERSION[ ]*=.+$")
string(REGEX REPLACE "^[ ]*VERSION[ ]*=[ ]*" "" FMT_VERSION "${VER}")
# allow user to override the download URL with a local tarball
# needed for offline build envs
if(FMT_URL)
get_filename_component(FMT_URL "${FMT_URL}" ABSOLUTE)
else()
set(FMT_URL http://mirrors.kodi.tv/build-deps/sources/fmt-${FMT_VERSION}.tar.gz)
endif()
if(VERBOSE)
message(STATUS "FMT_URL: ${FMT_URL}")
endif()
if(APPLE)
set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}")
endif()
set(FMT_LIBRARY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/lib/libfmt.a)
set(FMT_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include)
externalproject_add(fmt
URL ${FMT_URL}
DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/download
PREFIX ${CORE_BUILD_DIR}/fmt
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_INSTALL_LIBDIR=lib
-DFMT_DOC=OFF
-DFMT_TEST=OFF
"${EXTRA_ARGS}"
BUILD_BYPRODUCTS ${FMT_LIBRARY})
set_target_properties(fmt PROPERTIES FOLDER "External Projects")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Fmt
REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR
VERSION_VAR FMT_VERSION)
set(FMT_LIBRARIES ${FMT_LIBRARY})
set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR})
else()
find_package(FMT 6.1.2 CONFIG REQUIRED QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_FMT libfmt QUIET)
if(PC_FMT_VERSION AND NOT FMT_VERSION)
set(FMT_VERSION ${PC_FMT_VERSION})
endif()
endif()
find_path(FMT_INCLUDE_DIR NAMES fmt/format.h
PATHS ${PC_FMT_INCLUDEDIR})
find_library(FMT_LIBRARY_RELEASE NAMES fmt
PATHS ${PC_FMT_LIBDIR})
find_library(FMT_LIBRARY_DEBUG NAMES fmtd
PATHS ${PC_FMT_LIBDIR})
include(SelectLibraryConfigurations)
select_library_configurations(FMT)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Fmt
REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR FMT_VERSION
VERSION_VAR FMT_VERSION)
if(FMT_FOUND)
set(FMT_LIBRARIES ${FMT_LIBRARY})
set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR})
if(NOT TARGET fmt)
add_library(fmt UNKNOWN IMPORTED)
set_target_properties(fmt PROPERTIES
IMPORTED_LOCATION "${FMT_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}")
endif()
endif()
endif()
mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY)

View File

@ -0,0 +1,113 @@
#
# This module detects if gsoap is installed and determines where the
# include files and libraries are.
#
# This code sets the following variables:
#
# GSOAP_IMPORT_DIR = full path to the gsoap import directory
# GSOAP_LIBRARIES = full path to the gsoap libraries
# GSOAP_SSL_LIBRARIES = full path to the gsoap ssl libraries
# GSOAP_INCLUDE_DIR = include dir to be used when using the gsoap library
# GSOAP_PLUGIN_DIR = gsoap plugins directory
# GSOAP_WSDL2H = wsdl2h binary
# GSOAP_SOAPCPP2 = soapcpp2 binary
# GSOAP_FOUND = set to true if gsoap was found successfully
#
# GSOAP_ROOT
# setting this enables search for gsoap libraries / headers in this location
# -----------------------------------------------------
# GSOAP Import Directories
# -----------------------------------------------------
find_path(GSOAP_IMPORT_DIR
NAMES wsa.h
PATHS ${GSOAP_ROOT}/import ${GSOAP_ROOT}/share/gsoap/import
)
# -----------------------------------------------------
# GSOAP Libraries
# -----------------------------------------------------
find_library(GSOAP_CXX_LIBRARIES
NAMES gsoap++
HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64
${GSOAP_ROOT}/lib32
DOC "The main gsoap library"
)
find_library(GSOAP_SSL_CXX_LIBRARIES
NAMES gsoapssl++
HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64
${GSOAP_ROOT}/lib32
DOC "The ssl gsoap library"
)
# -----------------------------------------------------
# GSOAP Include Directories
# -----------------------------------------------------
find_path(GSOAP_INCLUDE_DIR
NAMES stdsoap2.h
HINTS ${GSOAP_ROOT} ${GSOAP_ROOT}/include ${GSOAP_ROOT}/include/*
DOC "The gsoap include directory"
)
# -----------------------------------------------------
# GSOAP plugin Directories
# -----------------------------------------------------
find_path(GSOAP_PLUGIN_DIR
NAMES wsseapi.c
HINTS ${GSOAP_ROOT} /usr/share/gsoap/plugin
DOC "The gsoap plugin directory"
)
# -----------------------------------------------------
# GSOAP Binaries
# ----------------------------------------------------
if(NOT GSOAP_TOOL_DIR)
set(GSOAP_TOOL_DIR GSOAP_ROOT)
endif()
find_program(GSOAP_WSDL2H
NAMES wsdl2h
HINTS ${GSOAP_TOOL_DIR}/bin
DOC "The gsoap bin directory"
)
find_program(GSOAP_SOAPCPP2
NAMES soapcpp2
HINTS ${GSOAP_TOOL_DIR}/bin
DOC "The gsoap bin directory"
)
# -----------------------------------------------------
# GSOAP version
# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16
# ----------------------------------------------------
if(GSOAP_SOAPCPP2)
execute_process(COMMAND ${GSOAP_SOAPCPP2} "-V" OUTPUT_VARIABLE GSOAP_STRING_VERSION ERROR_VARIABLE GSOAP_STRING_VERSION )
string(REGEX MATCH "[0-9]*\\.[0-9]*\\.[0-9]*" GSOAP_VERSION ${GSOAP_STRING_VERSION})
endif()
# -----------------------------------------------------
# GSOAP_276_COMPAT_FLAGS and GSOAPVERSION
# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16
# ----------------------------------------------------
if( "${GSOAP_VERSION}" VERSION_LESS "2.7.6")
set(GSOAP_276_COMPAT_FLAGS "")
elseif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14")
set(GSOAP_276_COMPAT_FLAGS "-z")
else ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14")
set(GSOAP_276_COMPAT_FLAGS "-z1 -z2")
endif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.6")
# -----------------------------------------------------
# handle the QUIETLY and REQUIRED arguments and set GSOAP_FOUND to TRUE if
# all listed variables are TRUE
# -----------------------------------------------------
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GSOAP DEFAULT_MSG GSOAP_CXX_LIBRARIES
GSOAP_INCLUDE_DIR GSOAP_WSDL2H GSOAP_SOAPCPP2)
mark_as_advanced(GSOAP_INCLUDE_DIR GSOAP_LIBRARIES GSOAP_WSDL2H GSOAP_SOAPCPP2)
if(GSOAP_FOUND)
if(GSOAP_FIND_REQUIRED AND GSOAP_FIND_VERSION AND ${GSOAP_VERSION} VERSION_LESS ${GSOAP_FIND_VERSION})
message(SEND_ERROR "Found GSOAP version ${GSOAP_VERSION} less then required ${GSOAP_FIND_VERSION}.")
endif()
endif()

View File

@ -47,5 +47,9 @@ ZM_PATH_SWAP=@ZM_TMPDIR@
# ZoneMinder will find the arp binary automatically on most systems
ZM_PATH_ARP="@ZM_PATH_ARP@"
# Full path to optional arp-scan binary
# ZoneMinder will find the arp-scan binary automatically on most systems
ZM_PATH_ARP_SCAN="@ZM_PATH_ARP_SCAN@"
#Full path to shutdown binary
ZM_PATH_SHUTDOWN="@ZM_PATH_SHUTDOWN@"

View File

@ -4,6 +4,7 @@
configure_file(zm_create.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" @ONLY)
configure_file(zm_update-1.31.30.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" @ONLY)
configure_file(zm_update-1.35.24.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" @ONLY)
configure_file(zm_update-1.37.4.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.sql" @ONLY)
# Glob all database upgrade scripts
file(GLOB dbfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "zm_update-*.sql")
@ -15,6 +16,8 @@ install(FILES ${dbfileslist} DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install zm_update-1.35.24.sql
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install zm_update-1.37.4.sql
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.37.4.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install zm_create.sql
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
@ -22,3 +25,8 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_I
# install triggers.sql
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/triggers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install manufacturers.sql
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/manufacturers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install models.sql
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/models.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")

24
db/manufacturers.sql Normal file
View File

@ -0,0 +1,24 @@
INSERT IGNORE INTO Manufacturers VALUES (1, 'Acti');
INSERT IGNORE INTO Manufacturers VALUES (2, 'Amcrest');
INSERT IGNORE INTO Manufacturers VALUES (3, 'Airlink101');
INSERT IGNORE INTO Manufacturers VALUES (4, 'Arecont Vision');
INSERT IGNORE INTO Manufacturers VALUES (5, 'Axis');
INSERT IGNORE INTO Manufacturers VALUES (6, 'Dahua');
INSERT IGNORE INTO Manufacturers VALUES (7, 'D-Link');
INSERT IGNORE INTO Manufacturers VALUES (8, 'Edimax');
INSERT IGNORE INTO Manufacturers VALUES (9, 'Foscam');
INSERT IGNORE INTO Manufacturers VALUES (10, 'Gadspot');
INSERT IGNORE INTO Manufacturers VALUES (11, 'GrandStream');
INSERT IGNORE INTO Manufacturers VALUES (12, 'HikVision');
INSERT IGNORE INTO Manufacturers VALUES (13, 'JVC');
INSERT IGNORE INTO Manufacturers VALUES (14, 'Maginon');
INSERT IGNORE INTO Manufacturers VALUES (15, 'Mobotix');
INSERT IGNORE INTO Manufacturers VALUES (16, 'Oncam Grandeye');
INSERT IGNORE INTO Manufacturers VALUES (17, 'Panasonic');
INSERT IGNORE INTO Manufacturers VALUES (18, 'Pelco');
INSERT IGNORE INTO Manufacturers VALUES (19, 'Sony');
INSERT IGNORE INTO Manufacturers VALUES (20, 'TP-Link');
INSERT IGNORE INTO Manufacturers VALUES (21, 'Trendnet');
INSERT IGNORE INTO Manufacturers VALUES (22, 'VisionTek');
INSERT IGNORE INTO Manufacturers VALUES (23, 'Vivotek');
INSERT IGNORE INTO Manufacturers VALUES (24, 'Wansview');

56
db/models.sql Normal file
View File

@ -0,0 +1,56 @@
/* INSERT INTO Manufacturers VALUES (1, 'Acti'); */
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A21');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A23');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A24');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A28');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A31');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A310');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A311');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A32');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A41');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A415');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A416');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A418');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A42');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A421');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A43');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A45');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A46');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A48');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (1, 'A74');
/*
INSERT INTO Manufacturers VALUES (2, 'Amcrest');
*/
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'IP8M-T2499EW');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (2, 'ASH42-B');
/*
INSERT INTO Manufacturers VALUES (3, 'Airlink101');
INSERT INTO Manufacturers VALUES (4, 'Arecont Vision');
INSERT INTO Manufacturers VALUES (5, 'Axis');
INSERT INTO Manufacturers VALUES (6, 'Dahua');
INSERT INTO Manufacturers VALUES (7, 'D-Link');
*/
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-930L');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-932L');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-933L');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-942L');
INSERT IGNORE INTO Models (ManufacturerId,Name) VALUES (7, 'DCS-5020L');
/*
INSERT INTO Manufacturers VALUES (8, 'Edimax');
INSERT INTO Manufacturers VALUES (9, 'Foscam');
INSERT INTO Manufacturers VALUES (10, 'Gadspot');
INSERT INTO Manufacturers VALUES (11, 'GrandStream');
INSERT INTO Manufacturers VALUES (12, 'HikVision');
INSERT INTO Manufacturers VALUES (13, 'JVC');
INSERT INTO Manufacturers VALUES (14, 'Maginon');
INSERT INTO Manufacturers VALUES (15, 'Mobotix');
INSERT INTO Manufacturers VALUES (16, 'Oncam Grandeye');
INSERT INTO Manufacturers VALUES (17, 'Panasonic');
INSERT INTO Manufacturers VALUES (18, 'Pelco');
INSERT INTO Manufacturers VALUES (19, 'Sony');
INSERT INTO Manufacturers VALUES (20, 'TP-Link');
INSERT INTO Manufacturers VALUES (21, 'Trendnet');
INSERT INTO Manufacturers VALUES (22, 'VisionTek');
INSERT INTO Manufacturers VALUES (23, 'Vivotek');
INSERT INTO Manufacturers VALUES (24, 'Wansview');
*/

View File

@ -39,6 +39,7 @@ CREATE TABLE `Config` (
`Help` text,
`Category` varchar(32) NOT NULL default '',
`Readonly` tinyint(3) unsigned NOT NULL default '0',
`Private` BOOLEAN NOT NULL DEFAULT FALSE,
`Requires` text,
PRIMARY KEY (`Name`)
) ENGINE=@ZM_MYSQL_ENGINE@;
@ -188,7 +189,7 @@ CREATE TABLE `Events` (
`StorageId` smallint(5) unsigned default 0,
`SecondaryStorageId` smallint(5) unsigned default 0,
`Name` varchar(64) NOT NULL default '',
`Cause` varchar(32) NOT NULL default '',
`Cause` TEXT,
`StartDateTime` datetime default NULL,
`EndDateTime` datetime default NULL,
`Width` smallint(5) unsigned NOT NULL default '0',
@ -283,6 +284,7 @@ CREATE TABLE `Filters` (
`Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '',
`UserId` int(10) unsigned,
`ExecuteInterval` int(10) unsigned NOT NULL default '60',
`Query_json` text NOT NULL,
`AutoArchive` tinyint(3) unsigned NOT NULL default '0',
`AutoUnarchive` tinyint(3) unsigned NOT NULL default '0',
@ -412,6 +414,7 @@ CREATE TABLE `Models` (
DROP TABLE IF EXISTS `MonitorPresets`;
CREATE TABLE `MonitorPresets` (
`Id` int(10) unsigned NOT NULL auto_increment,
`ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id),
`Name` varchar(64) NOT NULL default '',
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local',
`Device` tinytext,
@ -447,16 +450,24 @@ CREATE TABLE `Monitors` (
`Notes` TEXT,
`ServerId` int(10) unsigned,
`StorageId` smallint(5) unsigned default 0,
`ManufacturerId` int unsigned, FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id),
`ModelId` int unsigned, FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id),
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local',
`Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor',
`Enabled` tinyint(3) unsigned NOT NULL default '1',
`DecodingEnabled` tinyint(3) unsigned NOT NULL default '1',
`JanusEnabled` BOOLEAN NOT NULL default false,
`JanusAudioEnabled` BOOLEAN NOT NULL default false,
`LinkedMonitors` varchar(255),
`Triggers` set('X10') NOT NULL default '',
`EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '',
`EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '',
`ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '',
`ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '',
`ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '',
`ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '',
`ONVIF_Event_Listener` BOOLEAN NOT NULL DEFAULT FALSE,
`use_Amcrest_API` BOOLEAN NOT NULL DEFAULT FALSE,
`Device` tinytext NOT NULL default '',
`Channel` tinyint(3) unsigned NOT NULL default '0',
`Format` int(10) unsigned NOT NULL default '0',
@ -971,81 +982,81 @@ INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0
-- Add some monitor preset values
--
INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<username>:<pwd>@<ip-address>','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,'<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video<?>','0',255,'http','simple','<ip-address>','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','<ip-address>',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>/axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>/axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@<host/address>:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','<ip-address>',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=<monitor-id>&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:554/11',NULL,704,576,0,NULL,1,'10','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:554/11',NULL,640,480,0,NULL,1,'11','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:88/videoMain',NULL,1280,720,0,NULL,1,'12','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'<ip-address>','80','/videostream.cgi?user=<username>&pwd=<password>&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','','<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'<username>:<password>@<ip-address>','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','','<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video<?>','0',255,'','rtpMulti','','80','rtsp://<ip-address>:554/11','',1920,1080,0,0.00,1,'16','-speed=64','<ip-address>:<port>',100,33);
INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp://<ip-address>/tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp://<ip-address>/tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 320x240, mpjpeg, B&W','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&color=0',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP, 640x480, mpjpeg, B&W','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&color=0',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,5.0,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=640x480&req_fps=5',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,NULL,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=640x480',NULL,640,480,3,5.0,1,4,NULL,'<ip-address>:<port>',100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, unicast','Remote','rtsp',0,255,'rtsp','rtpUni','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<username>:<pwd>@<ip-address>','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,'<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,NULL,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Panasonic IP PTZ, 640x480, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=640x480&Quality=Standard',NULL,640,480,3,5.0,1,5,NULL,'<ip-address>:<port>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video<?>','0',255,'http','simple','<ip-address>','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','<ip-address>',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>/axis-media/media.amp?videocodec=h264',NULL,NULL,NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Vivotek FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>:554/live.sdp',NULL,NULL,NULL,352,240,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Axis FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://<host/address>/axis-media/media.amp',NULL,NULL,NULL,640,480,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'ACTi TCM FFMPEG','Ffmpeg',NULL,NULL,NULL,NULL,NULL,'rtsp://admin:123456@<host/address>:7070',NULL,NULL,NULL,320,240,NULL,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 320x240, max 5 FPS','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), PAL, 640x480, max 5 FPS','Local','/dev/video<?>',0,255,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 320x240, max 5 FPS','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,320,240,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L2), NTSC, 640x480, max 5 FPS','Local','/dev/video<?>',0,45056,NULL,'v4l2',NULL,NULL,NULL,NULL,640,480,1345466932,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 320x240, max 5 FPS','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), PAL, 640x480, max 5 FPS','Local','/dev/video<?>',0,0,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 320x240, max 5 FPS','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,320,240,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'BTTV Video (V4L1), NTSC, 640x480, max 5 FPS','Local','/dev/video<?>',0,1,NULL,'v4l1',NULL,NULL,NULL,NULL,640,480,13,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Remote ZoneMinder','Remote',NULL,NULL,NULL,'http','simple','<ip-address>',80,'/cgi-bin/nph-zms?mode=jpeg&monitor=<monitor-id>&scale=100&maxfps=5&buffer=0',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8620 FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:554/11',NULL,704,576,0,NULL,1,'10','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI8608W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:554/11',NULL,640,480,0,NULL,1,'11','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',NULL,NULL,NULL,NULL,'','','','rtsp://<username>:<pwd>@<ip-address>:88/videoMain',NULL,1280,720,0,NULL,1,'12','<admin_pwd>','<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'<ip-address>','80','/videostream.cgi?user=<username>&pwd=<password>&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','','<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'<username>:<password>@<ip-address>','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','','<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video<?>','0',255,'','rtpMulti','','80','rtsp://<ip-address>:554/11','',1920,1080,0,0.00,1,'16','-speed=64','<ip-address>:<port>',100,33);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp://<ip-address>/tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp',NULL,554,'rtsp://<ip-address>/tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100);
--
-- Add some zone preset values
@ -1115,6 +1126,9 @@ CREATE TABLE Snapshot_Events (
-- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts.
source @PKGDATADIR@/db/triggers.sql
source @PKGDATADIR@/db/manufacturers.sql
source @PKGDATADIR@/db/models.sql
--
-- Apply the initial configuration
--

View File

@ -56,7 +56,7 @@ EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitor_Status'
AND column_name = 'DayEvents'
AND column_name = 'DayEventDiskSpace'
) > 0,
"ALTER TABLE `Monitor_Status` DROP `DayEventDiskSpace`",
"SELECT 'Column DayEventDiskSpace already removed from Monitor_Status'"

47
db/zm_update-1.35.29.sql Normal file
View File

@ -0,0 +1,47 @@
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ManufacturerId'
) > 0,
"SELECT 'Column ManufacturerId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ManufacturerId'
) > 0,
"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ModelId'
) > 0,
"SELECT 'Column ModelId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ModelId'
) > 0,
"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

20
db/zm_update-1.37.10.sql Normal file
View File

@ -0,0 +1,20 @@
--
-- Update Config table to have Private
--
SELECT 'Checking for Private in Config';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Config'
AND table_schema = DATABASE()
AND column_name = 'Private'
) > 0,
"SELECT 'Column Private already exists in Config'",
"ALTER TABLE `Config` ADD COLUMN `Private` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Readonly`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

18
db/zm_update-1.37.11.sql Normal file
View File

@ -0,0 +1,18 @@
--
-- Update Monitors table to have use_Amcrest_API
--
SELECT 'Checking for use_Amcrest_API in Monitors';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'use_Amcrest_API'
) > 0,
"SELECT 'Column use_Amcrest_API already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `use_Amcrest_API` BOOLEAN NOT NULL default false AFTER `ONVIF_Event_Listener`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

73
db/zm_update-1.37.3.sql Normal file
View File

@ -0,0 +1,73 @@
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ManufacturerId'
) > 0,
"SELECT 'Column ManufacturerId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ManufacturerId'
) > 0,
"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ModelId'
) > 0,
"SELECT 'Column ModelId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'ModelId'
) > 0,
"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'",
"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'MonitorPresets'
AND column_name = 'ModelId'
) > 0,
"SELECT 'Column ModelId already exists in MonitorPresets'",
"ALTER TABLE `MonitorPresets` ADD `ModelId` int(10) unsigned AFTER `Id`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE()
AND table_name = 'MonitorPresets'
AND column_name = 'ModelId'
) > 0,
"SELECT 'FOREIGN KEY for ModelId already exists in MonitorPresets'",
"ALTER TABLE `MonitorPresets` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
UPDATE `MonitorPresets` SET `ModelId`=(SELECT `Id` FROM `Models` WHERE `Name`='IP8M-T2499EW') WHERE `Name` like 'Amcrest, IP8M-T2499EW
%';

View File

@ -0,0 +1,2 @@
source @PKGDATADIR@/db/manufacturers.sql
source @PKGDATADIR@/db/models.sql

31
db/zm_update-1.37.5.sql Normal file
View File

@ -0,0 +1,31 @@
--
-- This update adds EventStartCommand and EventEndCommand
--
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'EventEndCommand'
) > 0,
"SELECT 'Column EventEndCommand already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`"
));
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 = 'EventStartCommand'
) > 0,
"SELECT 'Column EventStartCommand already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

18
db/zm_update-1.37.6.sql Normal file
View File

@ -0,0 +1,18 @@
--
-- Update Filters table to have a ExecuteInterval Column
--
SELECT 'Checking for ExecuteInterval in Filters';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Filters'
AND table_schema = DATABASE()
AND column_name = 'ExecuteInterval'
) > 0,
"SELECT 'Column ExecuteInterval already exists in Filters'",
"ALTER TABLE Filters ADD COLUMN `ExecuteInterval` int(10) unsigned NOT NULL default '60' AFTER `UserId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

21
db/zm_update-1.37.7.sql Normal file
View File

@ -0,0 +1,21 @@
/* Change Cause from varchar(32) to TEXT. We now include alarmed zone name */
ALTER TABLE `Events` MODIFY `Cause` TEXT;
--
-- Update Monitors table to have a ONVIF_Event_Listener Column
--
SELECT 'Checking for ONVIF_Event_Listener in Monitors';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'ONVIF_Event_Listener'
) > 0,
"SELECT 'Column ONVIF_Event_Listener already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Event_Listener` BOOLEAN NOT NULL default false AFTER `ONVIF_Options`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

18
db/zm_update-1.37.8.sql Normal file
View File

@ -0,0 +1,18 @@
--
-- Update Monitors table to have JanusEnabled
--
SELECT 'Checking for JanusEnabled in Monitors';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'JanusEnabled'
) > 0,
"SELECT 'Column JanusEnabled already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `JanusEnabled` BOOLEAN NOT NULL default false AFTER `DecodingEnabled`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

18
db/zm_update-1.37.9.sql Normal file
View File

@ -0,0 +1,18 @@
--
-- Update Monitors table to have JanusEnabled
--
SELECT 'Checking for JanusAudioEnabled in Monitors';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'JanusAudioEnabled'
) > 0,
"SELECT 'Column JanusAudioEnabled already exists in Monitors'",
"ALTER TABLE `Monitors` ADD COLUMN `JanusAudioEnabled` BOOLEAN NOT NULL default false AFTER `JanusEnabled`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

@ -1 +1 @@
Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878
Subproject commit eab32851421ffe54fec0229c3efc44c642bc8d46

View File

@ -9,7 +9,7 @@
%global ceb_version 1.0-zm
# RtspServer is configured as a git submodule
%global rtspserver_commit cd7fd49becad6010a1b8466bfebbd93999a39878
%global rtspserver_commit eab32851421ffe54fec0229c3efc44c642bc8d46
%global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt
%global sslkey %{_sysconfdir}/pki/tls/private/localhost.key
@ -36,7 +36,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.37.1
Version: 1.37.11
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
@ -354,7 +354,8 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
%{_unitdir}/zoneminder.service
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
%{_datadir}/polkit-1/actions/com.zoneminder.*
%{_datadir}/polkit-1/rules.d/com.zoneminder.arp-scan.rules
%{_bindir}/zmsystemctl.pl
%{_bindir}/zmaudit.pl

View File

@ -16,7 +16,6 @@ Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-ap
,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev
,libturbojpeg0-dev
,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat
,libpcre3-dev
,libpolkit-gobject-1-dev
,libv4l-dev [!hurd-any]
,libvlc-dev
@ -32,6 +31,7 @@ Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-ap
,libvncserver-dev
,libfmt-dev
,libjwt-gnutls-dev|libjwt-dev
,libgsoap-dev
Standards-Version: 4.5.0
Homepage: https://www.zoneminder.com/
@ -44,7 +44,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libswscale5|libswscale4
,libswresample3|libswresample2
,ffmpeg
,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl
,libcurl4, libcurl4-gnutls-dev
,libdatetime-perl, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl
,libdbd-mysql-perl
,libphp-serialization-perl
,libmodule-load-conditional-perl
@ -72,12 +73,12 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,policykit-1
,rsyslog | system-log-daemon
,zip
,libpcre3
,libcrypt-eksblowfish-perl
,libdata-entropy-perl
,libvncclient1|libvncclient0
,libfmt
,libjwt-gnutls0|libjwt0
,libgsoap-2.8.104|libgsoap-2.8.91|libgsoap-2.8.75|libgsoap-2.8.60|libgsoap10
Recommends: ${misc:Recommends}
,libapache2-mod-php | php-fpm

View File

@ -19,6 +19,7 @@ override_dh_auto_configure:
-DCMAKE_VERBOSE_MAKEFILE=ON \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_MAN=0 \
-DZM_NO_PCRE=ON \
-DZM_CONFIG_DIR="/etc/zm" \
-DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \
-DZM_RUNDIR="/run/zm" \

View File

@ -5,7 +5,8 @@
Description=ZoneMinder CCTV recording and surveillance system
After=network.target mysql.service
# Remarked out so that it will start ZM on machines that don't have mysql installed
#Requires=mysql.service
# Override it by placing an override.conf in /etc/systemd/system/zoneminder.service.d
#BindsTo=mysql.service
[Service]
#User=www-data

View File

@ -4,7 +4,7 @@ Debian
.. contents::
Easy Way: Debian 11 (Bullseye)
------------------------
------------------------------
This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye).
@ -35,7 +35,7 @@ Run the following commands.
sudo apt install mariadb-server
sudo apt install zoneminder
When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``.
By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only available to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_.
**Step 3:** Setup permissions for zm.conf
@ -104,7 +104,7 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file
You can do this using:
.. code-block::
::
echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list
@ -337,3 +337,6 @@ Reload Apache to enable your changes and then start ZoneMinder.
sudo systemctl start zoneminder
You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer.
.. _unix socket authentication: https://mariadb.com/kb/en/authentication-plugin-unix-socket/
.. _mariadb-secure-installation: https://mariadb.com/kb/en/mysql_secure_installation/

View File

@ -7,6 +7,8 @@ configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @O
configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY)
configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY)
configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" @ONLY)
configure_file(com.zoneminder.arp-scan.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" @ONLY)
configure_file(com.zoneminder.arp-scan.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" @ONLY)
configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY)
configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY)
configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY)
@ -19,6 +21,8 @@ configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY)
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")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d")
endif()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<vendor>The ZoneMinder Project</vendor>
<vendor_url>http://www.zoneminder.com/</vendor_url>
<action id="com.zoneminder.policykit.pkexec.run-arp-scan">
<description>Allow the ZoneMinder webuser to run arp-scan</description>
<message>The ZoneMinder webuser is trusted to run arp-scan</message>
<defaults>
<allow_any>yes</allow_any>
<allow_inactive>yes</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/arp-scan</annotate>
</action>
</policyconfig>

View File

@ -0,0 +1,7 @@
polkit.addRule(function(action, subject) {
if (action.id == "com.zoneminder.policykit.pkexec.run-arp-scan" &&
subject.user != "@WEB_USER@") {
return polkit.Result.NO;
}
});

50
misc/janus.jcfg Normal file
View File

@ -0,0 +1,50 @@
general: {
configs_folder = "/usr/local/etc/janus" # Configuration files folder
plugins_folder = "/usr/local/lib/janus/plugins" # Plugins folder
transports_folder = "/usr/local/lib/janus/transports" # Transports folder
events_folder = "/usr/local/lib/janus/events" # Event handlers folder
loggers_folder = "/usr/local/lib/janus/loggers" # External loggers folder
debug_level = 4 # Debug/logging level, valid values are 0-7
admin_secret = "janusoverlord" # String that all Janus requests must contain
protected_folders = [
"/bin",
"/boot",
"/dev",
"/etc",
"/initrd",
"/lib",
"/lib32",
"/lib64",
"/proc",
"/sbin",
"/sys",
"/usr",
"/var",
"/opt/janus/bin",
"/opt/janus/etc",
"/opt/janus/include",
"/opt/janus/lib",
"/opt/janus/lib32",
"/opt/janus/lib64",
"/opt/janus/sbin"
]
}
media: {
#ipv6 = true
#ipv6_linklocal = true
rtp_port_range = "20000-40000"
}
nat: {
nice_debug = false
ignore_mdns = true
keep_private_host = true
ice_ignore_list = "vmnet"
}
plugins: {
disable = "libjanus_audiobridge.so,libjanus_echotest.so,libjanus_recordplay.so,libjanus_sip.so,libjanus_textroom.so,libjanus_videocall.so,libjanus_videoroom.so,libjanus_voicemail.so,
libjanus_nosip.so"
}
transports: {
disable = "libjanus_rabbitmq.so, libjanus_pfunix.so,libjanus_websockets.so"
}

View File

@ -0,0 +1,4 @@
general: {
admin_key = "supersecret"
rtp_port_range = "20000-40000"
}

View File

@ -0,0 +1,25 @@
general: {
json = "indented" # Whether the JSON messages should be indented (default),
base_path = "/janus" # Base path to bind to in the web server (plain HTTP only)
http = true # Whether to enable the plain HTTP interface
port = 8088 # Web server HTTP port
https = false # Whether to enable HTTPS (default=false)
}
# Janus can also expose an admin/monitor endpoint, to allow you to check
# which sessions are up, which handles they're managing, their current
# status and so on. This provides a useful aid when debugging potential
# issues in Janus. The configuration is pretty much the same as the one
# already presented above for the webserver stuff, as the API is very
# similar: choose the base bath for the admin/monitor endpoint (/admin
# by default), ports, etc. Besides, you can specify
# a secret that must be provided in all requests as a crude form of
# authorization mechanism, and partial or full source IPs if you want to
# limit access basing on IP addresses. For security reasons, this
# endpoint is disabled by default, enable it by setting admin_http=true.
admin: {
admin_base_path = "/admin" # Base path to bind to in the admin/monitor web server (plain HTTP only)
admin_http = false # Whether to enable the plain HTTP interface
admin_port = 7088 # Admin/monitor web server HTTP port
admin_https = false # Whether to enable HTTPS (default=false)
}

View File

@ -3,8 +3,8 @@
[Unit]
Description=ZoneMinder CCTV recording and security system
After=network.target mysqld.service httpd.service
Requires=mysqld.service httpd.service
After=network.target mysqld.service httpd.service janus.service
Requires=mysqld.service httpd.service janus.service
[Service]
User=@WEB_USER@

View File

@ -156,7 +156,7 @@ sub loadConfigFromDB {
print("Error: unable to load options from database: $DBI::errstr\n");
return(0);
}
my $sql = "select * from Config";
my $sql = 'SELECT * FROM Config';
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
@ -203,7 +203,7 @@ sub saveConfigToDB {
my $res = $dbh->do( $sql )
or croak( "Can't do '$sql': ".$dbh->errstr() );
$sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?";
$sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Private = ?, Requires = ?";
my $sth = $dbh->prepare_cached( $sql )
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
foreach my $option ( @options ) {
@ -240,6 +240,7 @@ sub saveConfigToDB {
$option->{help},
$option->{category},
$option->{readonly} ? 1 : 0,
$option->{private} ? 1 : 0,
$option->{db_requires}
) or croak("Can't execute when updating config entry $$option{name}: ".$sth->errstr() );
} # end foreach option

View File

@ -304,6 +304,7 @@ our @options = (
{ name=>'ZM_AUTH_RELAY', value=>'hashed' }
],
type => $types{string},
private => 1,
category => 'system',
},
{
@ -370,6 +371,28 @@ our @options = (
type => $types{boolean},
category => 'system',
},
{
name => 'ZM_JANUS_SECRET',
default => '',
description => 'Password for Janus streaming administration.',
help => q`This value should be set to a secure password,
and match the admin_key value in janus.plugin.streaming.config.
`,
type => $types{string},
category => 'system',
},
{
name => 'ZM_JANUS_PATH',
default => '',
description => 'URL for Janus HTTP/S port',
help => q`Janus requires HTTP/S communication to administer
and initiate h.264 streams. If left blank, this will default to
the ZM hostname, port 8088/janus. This setting is particularly
useful for putting janus behind a reverse proxy.
`,
type => $types{string},
category => 'system',
},
{
name => 'ZM_ENABLE_CSRF_MAGIC',
default => 'yes',
@ -462,6 +485,7 @@ our @options = (
{name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'}
],
type => $types{string},
private => 1,
category => 'system',
},
{
@ -477,6 +501,7 @@ our @options = (
{name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'}
],
type => $types{string},
private => 1,
category => 'system',
},
{

View File

@ -51,11 +51,21 @@ sub open {
my $self = shift;
$self->loadMonitor();
if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
# Has no scheme at the beginning, so won't parse as a URI
$self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress};
}
$uri = URI->new($self->{Monitor}->{ControlAddress});
if ($self->{Monitor}->{ControlAddress} and ($self->{Monitor}->{ControlAddress} ne 'user:pass@ip')) {
Debug("Getting connection details from Control Address " . $self->{Monitor}->{ControlAddress});
if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
# Has no scheme at the beginning, so won't parse as a URI
$self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress};
}
$uri = URI->new($self->{Monitor}->{ControlAddress});
} elsif ($self->{Monitor}->{Path}) {
Debug("Getting connection details from Path " . $self->{Monitor}->{Path});
$uri = URI->new($self->{Monitor}->{Path});
$uri->scheme('http');
$uri->port(80);
$uri->path('');
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
@ -64,6 +74,7 @@ sub open {
$self->{state} = 'closed';
my ( $username, $password, $host ) = ( $uri->authority() =~ /^([^:]+):([^@]*)@(.+)$/ );
Debug("Have username: $username password: $password host: $host from authority:" . $uri->authority());
$uri->userinfo(undef);
@ -75,40 +86,47 @@ sub open {
# test auth
my $res = $self->{ua}->get($uri->canonical().$url);
if ( $res->is_success ) {
if ( $res->content() ne "Properties.PTZ.PTZ=yes\n" ) {
if ($res->is_success) {
if ($res->content() ne "Properties.PTZ.PTZ=yes\n") {
Warning('Response suggests that camera doesn\'t support PTZ. Content:('.$res->content().')');
}
$self->{state} = 'open';
return;
}
if ($res->status_line() eq '404 Not Found') {
#older style
$url = 'axis-cgi/com/ptz.cgi';
$res = $self->{ua}->get($uri->canonical().$url);
Debug("Result from getting ".$uri->canonical().$url . ':' . $res->status_line());
}
if ( $res->status_line() eq '401 Unauthorized' ) {
if ($res->status_line() eq '401 Unauthorized') {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
}
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
if ( $realm ne $1 ) {
$realm = $1;
$self->{ua}->credentials($uri->host_port(), $realm, $username, $password);
$res = $self->{ua}->get($uri->canonical().$url);
if ( $res->is_success() ) {
Info("Auth succeeded after setting realm to $realm. You can set this value in the Control Device field to speed up connections and remove these log entries.");
$self->{state} = 'open';
return;
foreach my $auth_header ( ref $$headers{'www-authenticate'} eq 'ARRAY' ? @{$$headers{'www-authenticate'}} : ($$headers{'www-authenticate'})) {
my ( $auth, $tokens ) = $auth_header =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
if ( $realm ne $1 ) {
$realm = $1;
$self->{ua}->credentials($uri->host_port(), $realm, $username, $password);
$res = $self->{ua}->get($uri->canonical().$url);
if ( $res->is_success() ) {
Info("Auth succeeded after setting realm to $realm. You can set this value in the Control Device field to speed up connections and remove these log entries.");
$self->{state} = 'open';
return;
}
Error('Authentication still failed after updating REALM status: '.$res->status_line);
} else {
Error('Authentication failed, not a REALM problem');
}
Error('Authentication still failed after updating REALM status: '.$res->status_line);
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
Error('Failed to match realm in tokens');
} # end if
} # end foreach auth header
} else {
Debug('No headers line');
} # end if headers

View File

@ -194,7 +194,6 @@ sub getCamParams {
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop {
my $self = shift;
@ -202,13 +201,19 @@ sub autoStop {
if ( $autostop ) {
Debug('Auto Stop');
my $cmd = 'onvif/PTZ';
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
usleep($autostop);
my $cmd = 'onvif/PTZ';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">'.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="0" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
$self->sendCmd($cmd, $msg, $content_type);
# Reported to not work, so superceded by the cmd above
$msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>' . $profileToken . '</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
$self->sendCmd($cmd, $msg, $content_type);
}
}
} # end sub autoStop
# Reset the Camera
sub reset {

View File

@ -374,6 +374,25 @@ sub reset {
$self->sendCmdPost($url,$cmd);
}
sub reboot {
my $self = shift;
Debug('Camera Reboot');
$self->sendCmdPost('/eng/admin/reboot.cgi', { reboot => 'true' });
#$referer = 'http://'.$HI->ip().'/eng/admin/tools_default.cgi';
#$initial_url = $HI->ip().'/eng/admin/tools_default.cgi';
}
sub ping {
return -1 if ! $ADDRESS;
require Net::Ping;
my $p = Net::Ping->new();
my $rv = $p->ping($ADDRESS);
$p->close();
return $rv;
}
1;
__END__

View File

@ -273,6 +273,9 @@ sub zmDbDo {
if ( ! defined $rows ) {
$sql =~ s/\?/'%s'/;
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
} elsif ( ZoneMinder::Logger::logLevel() > INFO ) {
$sql =~ s/\?/'%s'/;
Debug(sprintf("Succeeded $sql : $rows rows affected", @_));
}
return $rows;
}

View File

@ -43,6 +43,7 @@ require Date::Parse;
require POSIX;
use Date::Format qw(time2str);
use Time::HiRes qw(gettimeofday tv_interval stat);
use Scalar::Util qw(looks_like_number);
#our @ISA = qw(ZoneMinder::Object);
use parent qw(ZoneMinder::Object);
@ -601,7 +602,7 @@ sub CopyTo {
# First determine if we can move it to the dest.
# We do this before bothering to lock the event
my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint
if ( ! $$NewStorage{Id} ) {
if ( ! looks_like_number($$NewStorage{Id}) ) {
return 'New storage does not have an id. Moving will not happen.';
} elsif ( $$NewStorage{Id} == $$self{StorageId} ) {
return 'Event is already located at ' . $NewPath;
@ -733,19 +734,22 @@ sub MoveTo {
my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit};
$ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction;
$self->lock_and_load(); # The fact that we are in a transaction might not imply locking
if (!$self->lock_and_load()) {
Warning('Unable to lock event record '.$$self{Id}); # The fact that we are in a transaction might not imply locking
$ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
return 'Unable to lock event record';
}
my $OldStorage = $self->Storage(undef);
my $error = $self->CopyTo($NewStorage);
return $error if $error;
if (!$error) {
# Succeeded in copying all files, so we may now update the Event.
$$self{StorageId} = $$NewStorage{Id};
$self->Storage($NewStorage);
$error .= $self->save();
# Succeeded in copying all files, so we may now update the Event.
$$self{StorageId} = $$NewStorage{Id};
$self->Storage($NewStorage);
$error .= $self->save();
# Going to leave it to upper layer as to whether we rollback or not
# Going to leave it to upper layer as to whether we rollback or not
}
$ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
return $error if $error;

View File

@ -56,6 +56,7 @@ $primary_key = 'Id';
%fields = map { $_ => $_ } qw(
Id
Name
ExecuteInterval
Query_json
AutoArchive
AutoUnarchive
@ -106,7 +107,6 @@ sub Execute {
$sql =~ s/zmSystemLoad/$load/g;
}
$sql .= ' FOR UPDATE' if $$self{LockRows};
Debug("Filter::Execute SQL ($sql)");
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
@ -230,8 +230,8 @@ sub Sql {
# PostCondition, so no further SQL
} else {
( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) {
# Empty value will result in () from split
foreach my $temp_value ( $stripped_value ne '' ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')';
} elsif ( $term->{attr} =~ /^MonitorName/ ) {
@ -250,7 +250,8 @@ sub Sql {
$$self{Server} = new ZoneMinder::Server($temp_value);
}
} elsif ( $term->{attr} eq 'StorageId' ) {
$value = "'$temp_value'";
# Empty means NULL, otherwise must be an integer
$value = $temp_value ne '' ? int($temp_value) : 'NULL';
$$self{Storage} = new ZoneMinder::Storage($temp_value);
} elsif ( $term->{attr} eq 'Name'
|| $term->{attr} eq 'Cause'
@ -322,7 +323,7 @@ sub Sql {
$self->{Sql} .= ' IS NOT '.$value;
} elsif ( $term->{op} eq '=[]' or $term->{op} eq 'IN' ) {
$self->{Sql} .= ' IN ('.join(',', @value_list).")";
} elsif ( $term->{op} eq '![]' ) {
} elsif ( $term->{op} eq '![]' or $term->{op} eq 'NOT IN') {
$self->{Sql} .= ' NOT IN ('.join(',', @value_list).')';
} elsif ( $term->{op} eq 'LIKE' ) {
$self->{Sql} .= ' LIKE '.$value;
@ -370,10 +371,7 @@ sub Sql {
if ( @auto_terms ) {
$sql .= ' AND ( '.join(' or ', @auto_terms).' )';
}
if ( !$filter_expr->{sort_field} ) {
$filter_expr->{sort_field} = 'StartDateTime';
$filter_expr->{sort_asc} = 0;
}
my $sort_column = '';
if ( $filter_expr->{sort_field} eq 'Id' ) {
$sort_column = 'E.Id';
@ -405,14 +403,21 @@ sub Sql {
$sort_column = 'E.MaxScore';
} elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) {
$sort_column = 'E.DiskSpace';
} else {
$sort_column = 'E.StartDateTime';
} elsif ( $filter_expr->{sort_field} ne '' ) {
$sort_column = 'E.'.$filter_expr->{sort_field};
}
my $sort_order = $filter_expr->{sort_asc} ? 'ASC' : 'DESC';
$sql .= ' ORDER BY '.$sort_column.' '.$sort_order;
if ( $filter_expr->{limit} ) {
if ( $sort_column ne '' ) {
$sql .= ' ORDER BY '.$sort_column.' '.($filter_expr->{sort_asc} ? 'ASC' : 'DESC');
}
if ($filter_expr->{limit}) {
$sql .= ' LIMIT 0,'.$filter_expr->{limit};
}
if ($$self{LockRows}) {
$sql .= ' FOR UPDATE';
if ($filter_expr->{skip_locked}) {
$sql .= ' SKIP LOCKED';
}
}
$self->{Sql} = $sql;
} # end if has Sql
return $self->{Sql};

View File

@ -28,6 +28,7 @@ our %EXPORT_TAGS = (
makePath
jsonEncode
jsonDecode
jsonLoad
systemStatus
packageControl
daemonControl
@ -536,6 +537,23 @@ sub jsonDecode {
return $result;
}
sub jsonLoad {
my $file = shift;
my $json = undef;
eval {
require File::Slurp;
my $contents = File::Slurp::read_file($file);
if (!$contents) {
Error("No contents for $file");
return $json;
}
require JSON;
$json = JSON::decode_json($contents);
};
Error($@) if $@;
return $json;
}
sub parseNameEqualsValueToHash {
my %settings;
foreach my $line ( split ( /\r?\n/, $_[0] ) ) {

View File

@ -638,6 +638,7 @@ sub logInit( ;@ ) {
$logger = ZoneMinder::Logger->new() if !$logger;
$logger->initialise(%options);
logSetSignal();
return $logger;
}
sub logReinit {

View File

@ -42,6 +42,7 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# will save memory.
our %EXPORT_TAGS = (
constants => [ qw(
STATE_UNKNOWN
STATE_IDLE
STATE_PREALARM
STATE_ALARM
@ -98,11 +99,12 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Config qw(:all);
use ZoneMinder::Logger qw(:all);
use constant STATE_IDLE => 0;
use constant STATE_PREALARM => 1;
use constant STATE_ALARM => 2;
use constant STATE_ALERT => 3;
use constant STATE_TAPE => 4;
use constant STATE_UNKNOWN => 0;
use constant STATE_IDLE => 1;
use constant STATE_PREALARM => 2;
use constant STATE_ALARM => 3;
use constant STATE_ALERT => 4;
use constant STATE_TAPE => 5;
use constant ACTION_GET => 1;
use constant ACTION_SET => 2;

View File

@ -56,6 +56,14 @@ $serial = $primary_key = 'Id';
Enabled
LinkedMonitors
Triggers
EventStartCommand
EventEndCommand
ONVIF_URL
ONVIF_Username
ONVIF_Password
ONVIF_Options
ONVIF_Event_Listener
use_Amcrest_API
Device
Channel
Format
@ -133,6 +141,9 @@ $serial = $primary_key = 'Id';
DefaultCodec
Latitude
Longitude
RTSPServer
RTSPStreamName
Importance
);
%defaults = (
@ -241,20 +252,26 @@ sub control {
my $command = shift;
my $process = shift;
if ( $command eq 'stop' or $command eq 'restart' ) {
if ( $process ) {
`/usr/bin/zmdc.pl stop $process -m $$monitor{Id}`;
if ($command eq 'stop' or $command eq 'restart') {
if ($process) {
ZoneMinder::General::runCommand("zmdc.pl stop $process -m $$monitor{Id}");
} else {
`/usr/bin/zmdc.pl stop zma -m $$monitor{Id}`;
`/usr/bin/zmdc.pl stop zmc -m $$monitor{Id}`;
if ($monitor->{Type} eq 'Local') {
ZoneMinder::General::runCommand('zmdc.pl stop zmc -d '.$monitor->{Device});
} else {
ZoneMinder::General::runCommand('zmdc.pl stop zmc -m '.$monitor->{Id});
}
}
}
if ( $command eq 'start' or $command eq 'restart' ) {
if ( $process ) {
`/usr/bin/zmdc.pl start $process -m $$monitor{Id}`;
ZoneMinder::General::runCommand("zmdc.pl start $process -m $$monitor{Id}");
} else {
`/usr/bin/zmdc.pl start zmc -m $$monitor{Id}`;
`/usr/bin/zmdc.pl start zma -m $$monitor{Id}`;
if ($monitor->{Type} eq 'Local') {
ZoneMinder::General::runCommand('zmdc.pl start zmc -d '.$monitor->{Device});
} else {
ZoneMinder::General::runCommand('zmdc.pl start zmc -m '.$monitor->{Id});
}
} # end if
}
} # end sub control
@ -326,18 +343,30 @@ sub resumeMotionDetection {
sub Control {
my $self = shift;
if ( ! exists $$self{Control}) {
require ZoneMinder::Control;
my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId});
if ($Control) {
require Module::Load::Conditional;
if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) {
Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR");
return undef;
if (!exists $$self{Control}) {
if ($$self{ControlId}) {
require ZoneMinder::Control;
my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId});
if ($Control) {
my $Protocol = $$Control{Protocol};
if (!$Protocol) {
Error("No protocol set in control $$Control{Id}, trying Name $$Control{Name}");
$Protocol = $$Control{Name};
}
require Module::Load::Conditional;
if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$Protocol => undef})) {
Error("Can't load ZoneMinder::Control::$Protocol\n$Module::Load::Conditional::ERROR");
return undef;
}
bless $Control, 'ZoneMinder::Control::'.$Protocol;
$$Control{MonitorId} = $$self{Id};
$$self{Control} = $Control;
} else {
Error("Unable to load control for control $$self{ControlId} for monitor $$self{Id}");
}
bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol};
$$Control{MonitorId} = $$self{Id};
$$self{Control} = $Control;
} else {
Info("No ControlId set in monitor $$self{Id}")
}
}
return $$self{Control};

View File

@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL;
if ( ! ( $db_field =~ /\?/ ) ) {
if ( @{$$search{$k}} != 1 ) {
push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
} else {
push @where, $db_field.'=?';
push @where, '`'.$db_field.'`=?';
} # end if
} else {
$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
foreach my $p_k ( keys %{$$search{$k}} ) {
my $v = $$search{$k}{$p_k};
if ( ref $v eq 'ARRAY' ) {
push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')';
push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')';
push @values, $p_k, @{$v};
} else {
push @where, $db_field.'=?';
push @where, '`'.$db_field.'`=?';
push @values, $p_k, $v;
} # end if
} # end foreach p_k
@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
push @where, $db_field.' IS NULL';
} else {
if ( ! ( $db_field =~ /\?/ ) ) {
push @where, $db_field .'=?';
push @where, '`'.$db_field .'`=?';
} else {
push @where, $db_field;
}

View File

@ -429,10 +429,20 @@ sub start {
# It's not running, or at least it's not been started by us
$process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef };
} elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at "
if ($process->{term_sent_at}) {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' was told to term at "
.strftime('%y/%m/%d %H:%M:%S', localtime($process->{term_sent_at}))
.", pid = $process->{pid}\n"
);
$process->{keepalive} = !undef;
$process->{delay} = 0;
delete $terminating_processes{$command};
} else {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at "
.strftime('%y/%m/%d %H:%M:%S', localtime($process->{started}))
.", pid = $process->{pid}\n"
);
);
}
return;
}
@ -523,7 +533,7 @@ sub send_stop {
."\n"
);
sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n";
return();
return ();
}
my $pid = $process->{pid};
@ -586,7 +596,7 @@ sub check_for_processes_to_kill {
sub stop {
my ( $daemon, @args ) = @_;
my $command = join(' ', $daemon, @args );
my $command = join(' ', $daemon, @args);
my $process = $cmd_hash{$command};
if ( !$process ) {
dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'");

View File

@ -21,28 +21,6 @@
#
# ==========================================================================
=head1 NAME
zmfilter.pl - ZoneMinder tool to filter events
=head1 SYNOPSIS
zmfilter.pl [-f <filter name>,--filter=<filter name>] [--filter_id=<filter id>] | -v, --version
=head1 DESCRIPTION
This script continuously monitors the recorded events for the given
monitor and applies any filters which would delete and/or upload
matching events.
=head1 OPTIONS
-f{filter name}, --filter={filter name} - The name of a specific filter to run
--filter_id={filter id} - The id of a specific filter to run
-v, --version - Print ZoneMinder version
=cut
use strict;
use bytes;
@ -160,10 +138,9 @@ $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $event_id = 0;
if ( !EVENT_PATH ) {
if (!EVENT_PATH) {
Error('No event path defined. Config was '.$Config{ZM_DIR_EVENTS});
die;
}
@ -195,26 +172,37 @@ if ( ! ( $filter_name or $filter_id ) ) {
my @filters;
my $last_action = 0;
while( !$zm_terminate ) {
while (!$zm_terminate) {
my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $now = time;
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) {
Debug('Reloading filters');
$last_action = $now;
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
}
foreach my $filter ( @filters ) {
foreach my $filter (@filters) {
last if $zm_terminate;
if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) {
my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : 0));
if ($$filter{last_ran} and ($elapsed < $$filter{ExecuteInterval})) {
my $filter_delay = $$filter{ExecuteInterval} - ($now - $$filter{last_ran});
$delay = $filter_delay if $filter_delay < $delay;
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed");
next;
}
if ($$filter{Concurrent} and !($filter_id or $filter_name)) {
my ( $proc ) = $0 =~ /(\S+)/;
my ( $id ) = $$filter{Id} =~ /(\d+)/;
Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}");
system(qq`$proc --filter "$$filter{Name}" &`);
system(qq`$proc --filter_id $id &`);
} else {
checkFilter($filter);
$$filter{last_ran} = $now;
}
}
} # end foreach filter
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
@ -384,11 +372,6 @@ sub checkFilter {
} # end if AutoCopy
if ( $filter->{UpdateDiskSpace} ) {
if ( $$filter{LockRows} ) {
$ZoneMinder::Database::dbh->begin_work();
$Event->lock_and_load();
}
my $old_diskspace = $$Event{DiskSpace};
my $new_diskspace = $Event->DiskSpace(undef);
@ -481,15 +464,18 @@ sub generateImage {
my $event_path = $Event->Path();
my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', $event_path, $frame->{FrameId});
my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', $event_path, $frame->{FrameId}) if $analyse;
my $video_path = sprintf('%s/%d-video.mp4', $event_path, $Event->{Id});
my $video_path = sprintf('%s/%s', $event_path, $Event->{DefaultVideo});
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 ) {
Debug("Using analysis and jpeg exists $analyse_image_path");
$image_path = $analyse_image_path;
} elsif ( -r $capture_image_path ) {
Debug("Using captures and jpeg exists $capture_image_path");
$image_path = $capture_image_path;
} elsif ( -r $video_path ) {
Debug("mp4 exists $video_path");
my $command ="ffmpeg -nostdin -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'";
#$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path";
my $output = qx($command);
@ -503,6 +489,8 @@ sub generateImage {
} else {
$image_path = $capture_image_path;
}
} else {
Debug("No files found at $analyse_image_path, $capture_image_path or $video_path");
}
return $image_path;
}
@ -740,7 +728,7 @@ sub substituteTags {
if ( -e $path ) {
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
} else {
Warning("Path to first image does not exist at $path");
Warning("Path to first image does not exist at $path for image $first_alarm_frame");
}
}
@ -1051,9 +1039,7 @@ sub executeCommand {
my $filter = shift;
my $Event = shift;
my $event_path = $Event->Path();
my $command = $filter->{AutoExecuteCmd}.' '.$event_path;
my $command = $filter->{AutoExecuteCmd}.' '.$Event->Path();
$command = substituteTags($command, $filter, $Event);
Info("Executing '$command'");
@ -1063,15 +1049,37 @@ sub executeCommand {
chomp($output);
Debug("Output: $output");
}
if ( $status ) {
if ($status) {
Error("Command '$command' exited with status: $status");
return 0;
} else {
my $sql = 'UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?';
my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute( $Event->{Id} )
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
ZoneMinder::Database::zmSQLExecute('UPDATE `Events` SET `Executed` = 1 WHERE `Id` = ?', $Event->{Id});
}
return( 1 );
return 1;
}
1;
__END__
=head1 NAME
zmfilter.pl - ZoneMinder tool to select events and perform actions on them
=head1 SYNOPSIS
zmfilter.pl [-f <filter name>,--filter=<filter name>] [--filter_id=<filter id>] [--daemon] | -v, --version
=head1 DESCRIPTION
This script performs a specified database query to select recorded events and performs specified actions on them
such as email reporting, deleting, moving, etc. If the --daemon option is given it will remain resident, repeating
the query and applying actions. This is normally managed by zmdc.pl however it can be used manually as well.
=head1 OPTIONS
-f{filter name}, --filter={filter name} - The name of a specific filter to run
--filter_id={filter id} - The id of a specific filter to run
--daemon - Causes zmfilter.pl to stay running endlessly repeating the filter(s).
-v, --version - Print ZoneMinder version
=cut

View File

@ -263,7 +263,10 @@ sub countQuery {
sub getMonitorRef {
my $dbh = shift;
my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS` FROM `Monitors`';
my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS`,
(SELECT Name FROM Manufacturers WHERE Manufacturers.Id = ManufacturerId),
(SELECT Name FROM Models WHERE Models.Id = ModelId)
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 $arrayref = $sth->fetchall_arrayref({});

View File

@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25;
@EXTRA_PERL_LIB@
use ZoneMinder;
use ZoneMinder::Monitor;
use ZoneMinder::Trigger::Channel::Inet;
use ZoneMinder::Trigger::Channel::Unix;
use ZoneMinder::Trigger::Channel::Serial;
@ -166,13 +167,9 @@ while (!$zm_terminate) {
foreach my $connection ( values(%spawned_connections) ) {
if ( vec($rout, $connection->fileno(), 1) ) {
Debug('Got input from spawned connection '
.$connection->name()
.' ('
.$connection->fileno()
.')'
);
.$connection->name().' ('.$connection->fileno().')');
my $messages = $connection->getMessages();
if ( defined($messages) ) {
if (defined($messages)) {
foreach my $message ( @$messages ) {
handleMessage($connection, $message);
}
@ -199,34 +196,28 @@ while (!$zm_terminate) {
# Check polled connections
foreach my $connection ( @in_poll_connections ) {
my $messages = $connection->getMessages();
if ( defined($messages) ) {
foreach my $message ( @$messages ) {
handleMessage($connection, $message);
}
if (defined($messages)) {
foreach my $message (@$messages) { handleMessage($connection, $message) };
}
}
# Check for alarms that might have happened
my @out_messages;
foreach my $monitor ( values %monitors ) {
if ( ! zmMemVerify($monitor) ) {
if (!$monitor->connect()) {
# 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.
Debug("Failed connect, putting on reloads");
push @needsReload, $monitor;
next;
}
my ( $state, $last_event ) = zmMemRead( $monitor,
[
my ($state, $last_event) = zmMemRead($monitor, [
'shared_data:state',
'shared_data:last_event'
]
);
]);
#print( "$monitor->{Id}: S:$state, LE:$last_event" );
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" );
if ( $state == STATE_ALARM or $state == STATE_ALERT ) {
if ($state == STATE_ALARM or $state == STATE_ALERT) {
# In alarm state
if ( !defined($monitor->{LastEvent})
or ($last_event != $monitor->{LastEvent})
@ -255,6 +246,7 @@ while (!$zm_terminate) {
}
$monitor->{LastState} = $state;
$monitor->{LastEvent} = $last_event;
$monitor->disconnect();
} # end foreach monitor
foreach my $connection ( @out_connections ) {
@ -304,15 +296,22 @@ while (!$zm_terminate) {
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) {
foreach my $monitor ( values(%monitors) ) {
zmMemInvalidate( $monitor ); # Free up any used memory handle
}
loadMonitors();
@needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
} elsif ( @needsReload ) {
foreach my $monitor ( @needsReload ) {
loadMonitor($monitor);
} elsif (@needsReload) {
foreach my $monitor (@needsReload) {
$monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id());
if ( $$monitor{Function} eq 'None' ) {
delete $monitors{$monitor->Id()};
} elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) {
delete $monitors{$monitor->Id()};
} else {
if ($monitor->connect()) {
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
}
}
@needsReload = ();
}
@ -323,40 +322,21 @@ while (!$zm_terminate) {
Info('Trigger daemon exiting');
exit;
sub loadMonitor {
my $monitor = shift;
Debug('Loading monitor '.$monitor);
zmMemInvalidate($monitor);
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
} # end sub loadMonitor
sub loadMonitors {
$monitor_reload_time = time();
my %new_monitors = ();
%monitors = ();
my $sql = 'SELECT * FROM `Monitors`
WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'.
( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
;
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal( "Can't execute: ".$sth->errstr() );
while ( my $monitor = $sth->fetchrow_hashref() ) {
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
foreach my $monitor ( ZoneMinder::Monitor->find(
Function=>['Modect','Mocord','Nodect','Record'],
($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()),
)) {
if ($monitor->connect()) { # This will re-init shared memory
$monitor->{LastState} = zmGetMonitorState($monitor);
$monitor->{LastEvent} = zmGetLastEvent($monitor);
}
$new_monitors{$monitor->{Id}} = $monitor;
$monitors{$monitor->{Id}} = $monitor;
} # end while fetchrow
%monitors = %new_monitors;
} # end sub loadMonitors
sub handleMessage {

View File

@ -2,7 +2,7 @@
#
# ==========================================================================
#
# ZoneMinder Update Script, $Date$, $Revision$
# ZoneMinder Update Script
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
@ -31,29 +31,30 @@ zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u <dbu
=head1 DESCRIPTION
This script just checks what the most recent release of ZoneMinder is
at the the moment. It will eventually be responsible for applying and
configuring upgrades etc, including on the fly upgrades.
This script checks what the most recent release of ZoneMinder is
at the the moment by downloading https://update.zoneminder.com/version.txt.
It can also apply and configure 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>, --version=<version> - Force upgrade to the current version from <version>
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
-d <dir>, --dir=<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.
If not interactive zmupdate.pl will stay running, checking every hour.
If interactive will try once, print out result and quit.
-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>, --version=<version> - Force upgrade to the current version from <version>
-u <dbuser>, --user=<dbuser> - Alternate DB user with privileges to alter DB
-p <dbpass>, --pass=<dbpass> - Password of alternate DB user with privileges to alter DB
-s, --super - Use system maintenance account on debian based systems instead of unprivileged account
-d <dir>, --dir=<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;
use warnings;
use bytes;
use version;
use Crypt::Eksblowfish::Bcrypt;
use Data::Entropy::Algorithms qw(rand_bits);
# ==========================================================================
#
@ -95,7 +96,7 @@ my $use_log = (($> == 0) || ($> == $web_uid));
logInit( toFile=>$use_log?DEBUG:NOLOG );
logSetSignal();
my $interactive = 1;
my $interactive = -t STDERR; # interactive if we have IO
my $check = 0;
my $freshen = 0;
my $rename = 0;
@ -122,9 +123,8 @@ GetOptions(
) or pod2usage(-exitstatus => -1);
my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } );
if ( !$dbh ) {
die "Unable to connect to db\n";
}
die "Unable to connect to db\n" if !$dbh;
$Config{ZM_DB_USER} = $dbUser;
$Config{ZM_DB_PASS} = $dbPass;
# we escape dbpass with single quotes so that $ in the password has no effect, but dbpass could have a ' in it.
@ -144,8 +144,10 @@ if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0))
pod2usage(-exitstatus => -1);
}
if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
print('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n");
if ($check) {
if (!$interactive) {
Info('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};
@ -153,16 +155,14 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
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();
zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion);
}
while ( 1 ) {
my $now = time();
if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) {
if ( !$interactive and $lastVersion and $lastCheck and (($now-$lastCheck) <= CHECK_INTERVAL) ) {
Debug("Not checking for updates since we already have less than " . CHECK_INTERVAL . " seconds ago.");
} else {
Info('Checking for updates');
use LWP::UserAgent;
@ -175,21 +175,18 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) {
my $res = $ua->request($req);
if ( $res->is_success ) {
$lastVersion = $res->content;
chomp($lastVersion);
my $latestVersion = $res->content;
chomp($latestVersion);
$lastCheck = $now;
Info('Got version: '.$lastVersion);
Info('Got version: '.$latestVersion);
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();
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion);
zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck);
if ($interactive) {
print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n");
exit(0);
}
} else {
Error('Error check failed: \''.$res->status_line().'\'');
}
@ -447,11 +444,6 @@ if ( $version ) {
print( "\nUpgrading database to version ".ZM_VERSION."\n" );
# Update config first of all
migratePaths();
ZoneMinder::Config::loadConfigFromDB();
ZoneMinder::Config::saveConfigToDB();
my $cascade = undef;
if ( $cascade || $version eq "1.19.0" ) {
# Patch the database
@ -1044,14 +1036,16 @@ sub patchDB {
} # end sub patchDB
sub migratePasswords {
print ("Migratings passwords, if any...\n");
use Crypt::Eksblowfish::Bcrypt;
use Data::Entropy::Algorithms qw(rand_bits);
print("Migratings passwords, if any...\n");
my $sql = 'SELECT * FROM `Users`';
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 $user = $sth->fetchrow_hashref() ) {
while ( my $user = $sth->fetchrow_hashref() ) {
my $scheme = substr($user->{Password}, 0, 1);
if ($scheme eq '*') {
print ('-->'.$user->{Username}." password will be migrated\n");
print('-->'.$user->{Username}." password will be migrated\n");
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
my $settings = '$2a$10$'.$salt;
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);

View File

@ -56,6 +56,7 @@ use constant START_DELAY => 30; # To give everything else time to start
@EXTRA_PERL_LIB@
use ZoneMinder;
use ZoneMinder::Storage;
use ZoneMinder::Monitor;
use POSIX;
use DBI;
use autouse 'Data::Dumper'=>qw(Dumper);
@ -66,7 +67,7 @@ $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
my $log = logInit();
logSetSignal();
my $zm_terminate = 0;
sub TermHandler {
@ -80,91 +81,76 @@ Info('Watchdog starting, pausing for '.START_DELAY.' seconds');
sleep(START_DELAY);
my $dbh = zmDbConnect();
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors';
my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
while (!$zm_terminate) {
while ( ! ( $dbh and $dbh->ping() ) ) {
if ( ! ( $dbh = zmDbConnect() ) ) {
while (!($dbh and $dbh->ping())) {
if (!($dbh = zmDbConnect())) {
sleep($Config{ZM_WATCH_CHECK_INTERVAL});
}
}
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal('Can\'t execute: '.$sth->errstr());
while( my $monitor = $sth->fetchrow_hashref() ) {
foreach my $monitor (ZoneMinder::Monitor->find($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ())) {
next if $monitor->{Function} eq 'None';
next if $monitor->{Type} eq 'WebSite';
my $now = time();
my $restart = 0;
if (zmMemVerify($monitor)) {
# Check we have got an image recently
my $capture_time = zmGetLastWriteTime($monitor);
if (!defined($capture_time)) {
# Can't read from shared data
Debug('LastWriteTime is not defined.');
zmMemInvalidate($monitor);
next;
}
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if (!$capture_time) {
my $startup_time = zmGetStartupTime($monitor);
if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) {
Warning(
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
);
$restart = 1;
} else {
# We can't get the last capture time so can't be sure it's died, it might just be starting up.
zmMemInvalidate($monitor);
next;
}
}
if (!$restart) {
my $max_image_delay = (
$monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1)
) ? (3/$monitor->{MaxFPS})
: $Config{ZM_WATCH_MAX_DELAY}
;
my $image_delay = $now - $capture_time;
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
if ( $image_delay > $max_image_delay ) {
Warning("Restarting capture daemon for "
.$monitor->{Name}.", time since last capture $image_delay seconds ($now-$capture_time)"
);
$restart = 1;
}
} # end if ! restart
} else {
zmMemInvalidate($monitor);
if (!zmMemVerify($monitor)) {
Info("Restarting capture daemon for $monitor->{Name}, shared data not valid");
$restart = 1;
$monitor->control('restart');
next;
}
if ($restart) {
my $command;
if ($monitor->{Type} eq 'Local') {
$command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else {
$command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
# Check we have got an image recently
my $capture_time = zmGetLastWriteTime($monitor);
if (!defined($capture_time)) {
# Can't read from shared data
Warning('LastWriteTime is not defined.');
next;
}
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if (!$capture_time) {
# We can't get the last capture time so can't be sure it's died, it might just be starting up.
my $startup_time = zmGetStartupTime($monitor);
if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) {
$log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(),
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
);
$monitor->control('restart');
}
runCommand($command);
} elsif ($monitor->{Function} ne 'Monitor') {
# Now check analysis daemon
$restart = 0;
next;
}
my $max_image_delay = (
$monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1)
) ? (3/$monitor->{MaxFPS})
: $Config{ZM_WATCH_MAX_DELAY}
;
my $image_delay = $now - $capture_time;
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
if ($image_delay > $max_image_delay) {
$log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(),
'Restarting capture daemon for '.$monitor->{Name}.
", time since last capture $image_delay seconds ($now-$capture_time)");
$monitor->control('restart');
next;
}
if ($monitor->{Function} ne 'Monitor') {
# Now check analysis thread
# Check we have got an image recently
my $image_time = zmGetLastReadTime($monitor);
if (!defined($image_time)) {
# Can't read from shared data
$restart = 1;
# Can't read from shared data
Error("Error reading shared data for $$monitor{Id} $$monitor{Name}");
$monitor->control('restart');
next;
} elsif (!$image_time) {
# We can't get the last capture time so can't be sure it's died.
#$restart = 1;
Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.");
Debug("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.");
} else {
my $max_image_delay = ( $monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0)
@ -175,26 +161,16 @@ while (!$zm_terminate) {
my $image_delay = $now-$image_time;
Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay");
if ($image_delay > $max_image_delay) {
Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
." time since last analysis $image_delay seconds ($now-$image_time)"
);
$restart = 1;
$log->logPrint(ZoneMinder::Logger::WARNING+$monitor->Importance(),
"daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
." time since last analysis $image_delay seconds ($now-$image_time)");
$monitor->control('restart');
next;
}
}
if ($restart) {
Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}");
my $command;
if ( $monitor->{Type} eq 'Local' ) {
$command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else {
$command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
}
runCommand($command);
} # end if restart
} # end if check analysis daemon
# Prevent open handles building up if we have connect to shared memory
zmMemInvalidate($monitor); # Close our file handle to the zmc process we are about to end
} # end foreach monitor
sleep($Config{ZM_WATCH_CHECK_INTERVAL});

View File

@ -6,6 +6,7 @@ configure_file(zm_config_data.h.in "${CMAKE_BINARY_DIR}/zm_config_data.h" @ONLY)
# Group together all the source files that are used by all the binaries (zmc, zmu, zms etc)
set(ZM_BIN_SRC_FILES
zm_analysis_thread.cpp
zm_poll_thread.cpp
zm_buffer.cpp
zm_camera.cpp
zm_comms.cpp
@ -32,6 +33,9 @@ set(ZM_BIN_SRC_FILES
zm_libvnc_camera.cpp
zm_local_camera.cpp
zm_monitor.cpp
zm_monitor_monitorlink.cpp
zm_monitor_janus.cpp
zm_monitor_amcrest.cpp
zm_monitorstream.cpp
zm_ffmpeg.cpp
zm_ffmpeg_camera.cpp
@ -60,12 +64,64 @@ set(ZM_BIN_SRC_FILES
zm_signal.cpp
zm_stream.cpp
zm_swscale.cpp
zm_time.cpp
zm_user.cpp
zm_utils.cpp
zm_videostore.cpp
zm_zone.cpp
zm_storage.cpp)
if(GSOAP_FOUND)
set(ZM_BIN_SRC_FILES
${ZM_BIN_SRC_FILES}
${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp
${CMAKE_BINARY_DIR}/generated/soapC.cpp
${GSOAP_PLUGIN_DIR}/smdevp.c
${GSOAP_PLUGIN_DIR}/mecevp.c
${GSOAP_PLUGIN_DIR}/wsaapi.c
${GSOAP_PLUGIN_DIR}/wsseapi.c
${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c
)
SET(GCC_COMPILE_FLAGS "-DWITH_OPENSSL -DWITH_DOM")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}")
#Create the directory that will host files generated by GSOAP
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)
#some files are generated by gsoap
set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapClientLib.c PROPERTIES GENERATED TRUE )
set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapC.c PROPERTIES GENERATED TRUE )
set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp PROPERTIES GENERATED TRUE )
set_source_files_properties( ${GSOAP_PLUGIN_DIR}/smdevp.c PROPERTIES LANGUAGE CXX)
set_source_files_properties( ${GSOAP_PLUGIN_DIR}/mecevp.c PROPERTIES LANGUAGE CXX)
set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsaapi.c PROPERTIES LANGUAGE CXX)
set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsseapi.c PROPERTIES LANGUAGE CXX)
set_source_files_properties( ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c PROPERTIES LANGUAGE CXX)
#Create a cmake target that generate gsoap files
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/generated/soapC.cpp
OUTPUT ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp
COMMAND ${GSOAP_WSDL2H} -d -P -O2 -o ${CMAKE_BINARY_DIR}/generated/bindings.h http://www.onvif.org/onvif/ver10/events/wsdl/event.wsdl
COMMAND echo '\#import \"wsse.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h
COMMAND echo '\#import \"struct_timeval.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h
COMMAND ${GSOAP_SOAPCPP2} -n -2 -C -I ${GSOAP_PLUGIN_DIR}/.. -I ${GSOAP_PLUGIN_DIR}/../import/ -I ${GSOAP_PLUGIN_DIR}/../custom/ -d ${CMAKE_BINARY_DIR}/generated -j -x ${CMAKE_BINARY_DIR}/generated/bindings.h
COMMENT "CREATING STUBS AND GLUE CODE"
)
add_custom_target(GSOAP_GENERATION_TARGET
DEPENDS ${CMAKE_BINARY_DIR}/generated/soapC.cpp
DEPENDS ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp
DEPENDS ${GSOAP_PLUGIN_DIR}/smdevp.c
DEPENDS ${GSOAP_PLUGIN_DIR}/mecevp.c
DEPENDS ${GSOAP_PLUGIN_DIR}/wsaapi.c
DEPENDS ${GSOAP_PLUGIN_DIR}/wsseapi.c
DEPENDS ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c
)
endif()
# A fix for cmake recompiling the source files for every target.
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
@ -74,6 +130,15 @@ target_include_directories(zm
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR})
if(GSOAP_FOUND)
target_include_directories(zm
PUBLIC
${CMAKE_BINARY_DIR}/generated
${GSOAP_PLUGIN_DIR}/..
${GSOAP_INCLUDE_DIR})
endif()
target_link_libraries(zm
PUBLIC
FFMPEG::avcodec
@ -88,6 +153,15 @@ target_link_libraries(zm
PRIVATE
zm-core-interface)
if(GSOAP_FOUND)
target_link_libraries(zm
PUBLIC
${GSOAP_CXX_LIBRARIES}
${GSOAP_SSL_CXX_LIBRARIES}
${OPENSSL_SSL_LIBRARY}
${OPENSSL_CRYPTO_LIBRARY})
endif()
if(${ZM_JWT_BACKEND} STREQUAL "jwt_cpp")
target_link_libraries(zm
PUBLIC
@ -109,6 +183,11 @@ add_executable(zms zms.cpp)
add_executable(zmu zmu.cpp)
add_executable(zmbenchmark zmbenchmark.cpp)
if(GSOAP_FOUND)
#Make sure that the client is compiled only after gsoap has been processed
add_dependencies(zmc GSOAP_GENERATION_TARGET)
endif()
target_link_libraries(zmc
PRIVATE
zm-core-interface

View File

@ -23,6 +23,14 @@ void AnalysisThread::Start() {
void AnalysisThread::Run() {
while (!(terminate_ or zm_terminate)) {
monitor_->Analyse();
// Some periodic updates are required for variable capturing framerate
if (!monitor_->Analyse()) {
if (!(terminate_ or zm_terminate)) {
// We only sleep when Analyse returns false because it is an error condition and we will spin like mad if it persists.
Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE);
Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count()));
std::this_thread::sleep_for(sleep_for);
}
}
}
}

View File

@ -65,7 +65,7 @@ unsigned int Buffer::expand(unsigned int count) {
int Buffer::read_into(int sd, unsigned int bytes) {
// Make sure there is enough space
this->expand(bytes);
Debug(3, "Reading %u btes", bytes);
Debug(3, "Reading %u bytes", bytes);
int bytes_read = ::read(sd, mTail, bytes);
if (bytes_read > 0) {
mTail += bytes_read;

View File

@ -245,6 +245,7 @@ class Socket : public CommsBase {
}
virtual ssize_t recv(std::string &msg) const {
msg.reserve(ZM_NETWORK_BUFSIZ);
std::vector<char> buffer(msg.capacity());
ssize_t nBytes;
if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) {

View File

@ -108,8 +108,8 @@ bool zmDbConnect() {
}
void zmDbClose() {
std::lock_guard<std::mutex> lck(db_mutex);
if (zmDbConnected) {
std::lock_guard<std::mutex> lck(db_mutex);
mysql_close(&dbconn);
// mysql_init() call implicitly mysql_library_init() but
// mysql_close() does not call mysql_library_end()
@ -238,8 +238,13 @@ zmDbQueue::~zmDbQueue() {
}
void zmDbQueue::stop() {
mTerminate = true;
{
std::unique_lock<std::mutex> lock(mMutex);
mTerminate = true;
}
mCondition.notify_all();
if (mThread.joinable()) mThread.join();
}
@ -251,11 +256,11 @@ void zmDbQueue::process() {
mCondition.wait(lock);
}
while (!mQueue.empty()) {
if (mQueue.size() > 10) {
if (mQueue.size() > 30) {
Logger *log = Logger::fetch();
Logger::Level db_level = log->databaseLevel();
log->databaseLevel(Logger::NOLOG);
Warning("db queue size has grown larger %zu than 10 entries", mQueue.size());
Warning("db queue size has grown larger %zu than 20 entries", mQueue.size());
log->databaseLevel(db_level);
}
std::string sql = mQueue.front();
@ -271,8 +276,10 @@ void zmDbQueue::process() {
void zmDbQueue::push(std::string &&sql) {
if (mTerminate) return;
std::unique_lock<std::mutex> lock(mMutex);
mQueue.push(std::move(sql));
{
std::unique_lock<std::mutex> lock(mMutex);
mQueue.push(std::move(sql));
}
mCondition.notify_all();
}

View File

@ -55,7 +55,7 @@ Event::Event(
alarm_frames(0),
alarm_frame_written(false),
tot_score(0),
max_score(0),
max_score(-1),
//path(""),
//snapshit_file(),
//alarm_file(""),
@ -65,7 +65,8 @@ Event::Event(
last_db_frame(0),
have_video_keyframe(false),
//scheme
save_jpegs(0)
save_jpegs(0),
terminate_(false)
{
std::string notes;
createNotes(notes);
@ -133,98 +134,22 @@ Event::Event(
);
id = zmDbDoInsert(sql);
if (!SetPath(storage)) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql);
if (result) {
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
if (!storage) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if (monitor->ServerId())
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql);
if (result) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
}
if (!storage) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
zmDbDo(sql);
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */
videoStore = new VideoStore(
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ),
( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ),
monitor );
if ( !videoStore->open() ) {
Warning("Failed to open videostore, turning on jpegs");
delete videoStore;
videoStore = nullptr;
if ( ! ( save_jpegs & 1 ) ) {
save_jpegs |= 1; // Turn on jpeg storage
sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id);
zmDbDo(sql);
}
} else {
std::string codec = videoStore->get_codec();
video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
}
} // end if GetOptVideoWriter
if (storage != monitor->getStorage())
delete storage;
thread_ = std::thread(&Event::Run, this);
}
Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
Debug(1, "Deleting event, calling stop");
Stop();
if (thread_.joinable()) {
// Should be. Issuing the stop and then getting the lock
Debug(1, "Joinable");
thread_.join();
} else {
Debug(1, "Not Joinable");
}
/* Close the video file */
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
if (videoStore != nullptr) {
Debug(4, "Deleting video store");
delete videoStore;
@ -291,6 +216,10 @@ void Event::createNotes(std::string &notes) {
}
} // void Event::createNotes(std::string &notes)
void Event::addNote(const char *cause, const std::string &note) {
noteSetMap[cause].insert(note);
}
bool Event::WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame) const {
int thisquality =
(alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ?
@ -372,7 +301,13 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
} // end if update
} // void Event::updateNotes(const StringSetMap &newNoteSetMap)
void Event::AddPacket(const std::shared_ptr<ZMPacket>&packet) {
void Event::AddPacket(ZMLockedPacket *packetlock) {
std::unique_lock<std::mutex> lck(packet_queue_mutex);
packet_queue.push(packetlock);
packet_queue_condition.notify_one();
}
void Event::AddPacket_(const std::shared_ptr<ZMPacket>&packet) {
have_video_keyframe = have_video_keyframe ||
( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) &&
( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) );
@ -486,12 +421,12 @@ void Event::AddFrame(Image *image,
Debug(1, "frames %d, score %d max_score %d", frames, score, max_score);
// If this is the first frame, we should add a thumbnail to the event directory
if ((frames == 1) || (score > (int)max_score)) {
if ((frames == 1) || (score > max_score)) {
write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it.
Debug(1, "Writing snapshot");
Debug(1, "Writing snapshot to %s", snapshot_file.c_str());
WriteFrameImage(image, timestamp, snapshot_file.c_str());
} else {
Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score);
Debug(1, "Not Writing snapshot because frames %d score %d > max %d", frames, score, max_score);
}
// We are writing an Alarm frame
@ -500,17 +435,19 @@ void Event::AddFrame(Image *image,
if (!alarm_frame_written) {
write_to_db = true; // OD processing will need it, so the db needs to know about it
alarm_frame_written = true;
Debug(1, "Writing alarm image");
WriteFrameImage(image, timestamp, alarm_file.c_str());
Debug(1, "Writing alarm image to %s", alarm_file.c_str());
if (!WriteFrameImage(image, timestamp, alarm_file.c_str())) {
Error("Failed to write alarm frame image to %s", alarm_file.c_str());
}
} else {
Debug(3, "Not Writing alarm image because alarm frame already written");
}
if (alarm_image and (save_jpegs & 2)) {
std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames);
Debug(1, "Writing analysis frame %d", frames);
Debug(1, "Writing analysis frame %d to %s", frames, event_file.c_str());
if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) {
Error("Failed to write analysis frame image");
Error("Failed to write analysis frame image to %s", event_file.c_str());
}
}
} // end if is an alarm frame
@ -523,11 +460,15 @@ void Event::AddFrame(Image *image,
bool db_frame = ( frame_type == BULK )
or ( frame_type == ALARM )
or ( frames == 1 )
or ( score > (int)max_score )
or ( score > max_score )
or ( monitor_state == Monitor::ALERT )
or ( monitor_state == Monitor::ALARM )
or ( monitor_state == Monitor::PREALARM );
if (score > max_score) {
max_score = score;
}
if (db_frame) {
Microseconds delta_time = std::chrono::duration_cast<Microseconds>(timestamp - start_time);
Debug(1, "Frame delta is %.2f s - %.2f s = %.2f s, score %u zone_stats.size %zu",
@ -546,7 +487,7 @@ void Event::AddFrame(Image *image,
or
(frame_type == BULK)
or
(fps and (frame_data.size() > fps))) {
(fps and (frame_data.size() > 5*fps))) {
Debug(1, "Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)",
frame_data.size(), write_to_db, fps, (frame_type == BULK));
WriteDbFrames();
@ -568,9 +509,6 @@ void Event::AddFrame(Image *image,
} // end if frame_type == BULK
} // end if db_frame
if (score > (int) max_score) {
max_score = score;
}
end_time = timestamp;
}
@ -650,3 +588,117 @@ bool Event::SetPath(Storage *storage) {
} // deep storage or not
return true;
} // end bool Event::SetPath
void Event::Run() {
Storage *storage = monitor->getStorage();
if (!SetPath(storage)) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql);
if (result) {
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
if (!storage) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if (monitor->ServerId())
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql);
if (result) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
}
if (!storage) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
zmDbDo(sql);
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
video_incomplete_path = path + "/" + video_incomplete_file;
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */
videoStore = new VideoStore(
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ),
( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ),
monitor );
if ( !videoStore->open() ) {
Warning("Failed to open videostore, turning on jpegs");
delete videoStore;
videoStore = nullptr;
if ( ! ( save_jpegs & 1 ) ) {
save_jpegs |= 1; // Turn on jpeg storage
zmDbDo(stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id));
}
} else {
std::string codec = videoStore->get_codec();
video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
}
} // end if GetOptVideoWriter
if (storage != monitor->getStorage())
delete storage;
std::unique_lock<std::mutex> lck(packet_queue_mutex);
// The idea is to process the queue no matter what so that all packets get processed.
// We only break if the queue is empty
while (true) {
if (!packet_queue.empty()) {
Debug(1, "adding packet");
const ZMLockedPacket * packet_lock = packet_queue.front();
this->AddPacket_(packet_lock->packet_);
delete packet_lock;
packet_queue.pop();
} else {
if (terminate_ or zm_terminate) {
Debug(1, "terminating");
break;
}
Debug(1, "waiting");
packet_queue_condition.wait(lck);
Debug(1, "wakeing");
}
}
}
int Event::MonitorId() {
return monitor->Id();
}

View File

@ -22,14 +22,21 @@
#include "zm_config.h"
#include "zm_define.h"
#include "zm_packet.h"
#include "zm_storage.h"
#include "zm_time.h"
#include "zm_utils.h"
#include "zm_zone.h"
#include <atomic>
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <set>
#include <thread>
class EventStream;
class Frame;
@ -77,8 +84,8 @@ class Event {
int frames;
int alarm_frames;
bool alarm_frame_written;
unsigned int tot_score;
unsigned int max_score;
int tot_score;
int max_score;
std::string path;
std::string snapshot_file;
std::string alarm_file;
@ -98,6 +105,15 @@ class Event {
void createNotes(std::string &notes);
std::queue<ZMLockedPacket *> packet_queue;
std::mutex packet_queue_mutex;
std::condition_variable packet_queue_condition;
void Run();
std::atomic<bool> terminate_;
std::thread thread_;
public:
static bool OpenFrameSocket(int);
static bool ValidateFrameSocket(int);
@ -110,41 +126,50 @@ class Event {
uint64_t Id() const { return id; }
const std::string &Cause() const { return cause; }
void addNote(const char *cause, const std::string &note);
int Frames() const { return frames; }
int AlarmFrames() const { return alarm_frames; }
SystemTimePoint StartTime() const { return start_time; }
SystemTimePoint EndTime() const { return end_time; }
TimePoint::duration Duration() const { return end_time - start_time; };
void AddPacket(const std::shared_ptr<ZMPacket> &p);
void AddPacket(ZMLockedPacket *);
void AddPacket_(const std::shared_ptr<ZMPacket> &p);
bool WritePacket(const std::shared_ptr<ZMPacket> &p);
bool SendFrameImage(const Image *image, bool alarm_frame=false);
bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const;
void updateNotes(const StringSetMap &stringSetMap);
void AddFrame(Image *image,
void AddFrame(Image *image,
SystemTimePoint timestamp,
const std::vector<ZoneStats> &stats,
int score = 0,
Image *alarm_image = nullptr);
void Stop() {
terminate_ = true;
packet_queue_condition.notify_all();
}
bool Stopped() const { return terminate_; }
private:
void WriteDbFrames();
bool SetPath(Storage *storage);
public:
static std::string getSubPath(tm time) {
std::string subpath = stringtf("%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 std::string getSubPath(time_t *time) {
tm time_tm = {};
localtime_r(time, &time_tm);
return Event::getSubPath(time_tm);
}
static std::string getSubPath(tm time) {
std::string subpath = stringtf("%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 std::string getSubPath(time_t *time) {
tm time_tm = {};
localtime_r(time, &time_tm);
return Event::getSubPath(time_tm);
}
const char* getEventFile() const {
return video_file.c_str();
@ -154,18 +179,6 @@ class Event {
return pre_alarm_count;
}
static void EmptyPreAlarmFrames() {
#if 0
while ( pre_alarm_count > 0 ) {
int i = pre_alarm_count - 1;
delete pre_alarm_data[i].image;
pre_alarm_data[i].image = nullptr;
if ( pre_alarm_data[i].alarm_frame ) {
delete pre_alarm_data[i].alarm_frame;
pre_alarm_data[i].alarm_frame = nullptr;
}
pre_alarm_count--;
}
#endif
pre_alarm_count = 0;
}
static void AddPreAlarmFrame(
@ -174,28 +187,11 @@ class Event {
int score=0,
Image *alarm_frame=nullptr
) {
#if 0
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 ) {
pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame);
}
#endif
pre_alarm_count++;
}
void SavePreAlarmFrames() {
#if 0
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);
}
#endif
EmptyPreAlarmFrames();
}
int MonitorId();
};
#endif // ZM_EVENT_H

View File

@ -68,17 +68,17 @@ bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_tim
curr_frame_id = 1; // curr_frame_id is 1-based
if (event_time >= event_data->start_time) {
Debug(2, "event time is after event start");
for (unsigned int i = 0; i < event_data->frame_count; i++) {
for (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) {
curr_frame_id = i + 1;
Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %ld",
Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %d",
FPSeconds(curr_stream_time.time_since_epoch()).count(),
curr_frame_id);
break;
}
} // end foreach frame
Debug(3, "Skipping %ld frames", event_data->frame_count);
Debug(3, "Skipping %d frames", event_data->frame_count);
} else {
Warning("Requested an event time less than the start of the event. event_time %" PRIi64 " < start_time %" PRIi64,
static_cast<int64>(std::chrono::duration_cast<Seconds>(event_time.time_since_epoch()).count()),
@ -90,13 +90,13 @@ bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_tim
bool EventStream::loadInitialEventData(
uint64_t init_event_id,
unsigned int init_frame_id
int init_frame_id
) {
loadEventData(init_event_id);
if ( init_frame_id ) {
if ( init_frame_id >= event_data->frame_count ) {
Error("Invalid frame id specified. %d > %lu", init_frame_id, event_data->frame_count);
Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count);
curr_stream_time = event_data->start_time;
curr_frame_id = 1;
} else {
@ -225,20 +225,24 @@ bool EventStream::loadEventData(uint64_t event_id) {
sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` "
"FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id);
result = zmDbFetch(sql);
if (!result) {
exit(-1);
}
event_data->n_frames = mysql_num_rows(result);
event_data->frames = new FrameData[event_data->frame_count];
if (event_data->frame_count < event_data->n_frames) {
event_data->frame_count = event_data->n_frames;
Warning("Event %" PRId64 " has more frames in the Frames table (%d) than in the Event record (%d)",
event_data->event_id, event_data->n_frames, event_data->frame_count);
}
int last_id = 0;
SystemTimePoint last_timestamp = event_data->start_time;
Microseconds last_delta = Seconds(0);
while ( ( dbrow = mysql_fetch_row(result) ) ) {
while ((dbrow = mysql_fetch_row(result))) {
int id = atoi(dbrow[0]);
//timestamp = atof(dbrow[1]);
Microseconds delta = std::chrono::duration_cast<Microseconds>(FPSeconds(atof(dbrow[2])));
@ -280,7 +284,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
// Incomplete events might not have any frame data
event_data->last_frame_id = last_id;
if ( mysql_errno(&dbconn) ) {
if (mysql_errno(&dbconn)) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
@ -310,7 +314,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
else
curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp;
}
Debug(2, "Event: %" PRIu64 ", Frames: %ld, Last Frame ID (%ld, Duration: %.2f s Frames Duration: %.2f s",
Debug(2, "Event: %" PRIu64 ", Frames: %d, Last Frame ID (%d, Duration: %.2f s Frames Duration: %.2f s",
event_data->event_id,
event_data->frame_count,
event_data->last_frame_id,
@ -342,12 +346,12 @@ void EventStream::processCommand(const CmdMsg *msg) {
if (
(mode == MODE_SINGLE || mode == MODE_NONE)
&&
((unsigned int)curr_frame_id == event_data->last_frame_id)
(curr_frame_id == event_data->last_frame_id)
) {
Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
curr_frame_id = 1;
} else {
Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld",
Debug(1, "mode is %s, current frame is %d, frame count is %d, last frame id is %d",
StreamMode_Strings[(int) mode].c_str(),
curr_frame_id,
event_data->frame_count,
@ -404,9 +408,9 @@ void EventStream::processCommand(const CmdMsg *msg) {
paused = true;
replay_rate = ZM_RATE_BASE;
step = 1;
if ( (unsigned int)curr_frame_id < event_data->last_frame_id )
if (curr_frame_id < event_data->last_frame_id)
curr_frame_id += 1;
Debug(1, "Got SLOWFWD command new frame id %ld", curr_frame_id);
Debug(1, "Got SLOWFWD command new frame id %d", curr_frame_id);
break;
case CMD_SLOWREV :
paused = true;
@ -414,7 +418,7 @@ void EventStream::processCommand(const CmdMsg *msg) {
step = -1;
curr_frame_id -= 1;
if ( curr_frame_id < 1 ) curr_frame_id = 1;
Debug(1, "Got SLOWREV command new frame id %ld", curr_frame_id);
Debug(1, "Got SLOWREV command new frame id %d", curr_frame_id);
break;
case CMD_FASTREV :
Debug(1, "Got FAST REV command");
@ -538,12 +542,12 @@ void EventStream::processCommand(const CmdMsg *msg) {
if ( curr_frame_id < 1 ) {
curr_frame_id = 1;
} else if ( (unsigned long)curr_frame_id > event_data->last_frame_id ) {
} else if (curr_frame_id > event_data->last_frame_id) {
curr_frame_id = event_data->last_frame_id;
}
curr_stream_time = event_data->frames[curr_frame_id-1].timestamp;
Debug(1, "Got SEEK command, to %f s (new current frame id: %ld offset %f s)",
Debug(1, "Got SEEK command, to %f s (new current frame id: %d offset %f s)",
FPSeconds(offset).count(),
curr_frame_id,
FPSeconds(event_data->frames[curr_frame_id - 1].offset).count());
@ -615,11 +619,11 @@ bool EventStream::checkEventLoaded() {
sql = stringtf(
"SELECT `Id` FROM `Events` WHERE `MonitorId` = %d AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1",
event_data->monitor_id, event_data->event_id);
} else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) {
} else if (curr_frame_id > event_data->last_frame_id) {
if (event_data->end_time.time_since_epoch() == Seconds(0)) {
// We are viewing an in-process event, so just reload it.
loadEventData(event_data->event_id);
if ( (unsigned int)curr_frame_id > event_data->last_frame_id )
if (curr_frame_id > event_data->last_frame_id)
curr_frame_id = event_data->last_frame_id;
return false;
}
@ -628,7 +632,7 @@ bool EventStream::checkEventLoaded() {
event_data->monitor_id, event_data->event_id);
} else {
// No event change required
Debug(3, "No event change required, as curr frame %ld <=> event frames %lu",
Debug(3, "No event change required, as curr frame %d <=> event frames %d",
curr_frame_id, event_data->frame_count);
return false;
}
@ -662,8 +666,8 @@ bool EventStream::checkEventLoaded() {
curr_frame_id = event_data->last_frame_id;
else
curr_frame_id = 1;
Debug(2, "New frame id = %ld", curr_frame_id);
start = std::chrono::system_clock::now();
Debug(2, "New frame id = %d", curr_frame_id);
start = std::chrono::steady_clock::now();
return true;
} else {
Debug(2, "No next event loaded using %s. Pausing", sql.c_str());
@ -689,22 +693,20 @@ bool EventStream::checkEventLoaded() {
Image * EventStream::getImage( ) {
std::string path = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", path.c_str(), event_data->path.c_str(), curr_frame_id);
Debug(2, "EventStream::getImage path(%s) from %s frame(%d) ", path.c_str(), event_data->path.c_str(), curr_frame_id);
Image *image = new Image(path.c_str());
return image;
}
bool EventStream::sendFrame(Microseconds delta_us) {
Debug(2, "Sending frame %ld", curr_frame_id);
Debug(2, "Sending frame %d", curr_frame_id);
std::string filepath;
struct stat filestat = {};
// 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 (event_data->SaveJPEGs & 1) {
filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
} else if (event_data->SaveJPEGs & 2) {
if ((frame_type == FRAME_ANALYSIS) && (event_data->SaveJPEGs & 2)) {
filepath = stringtf(staticConfig.analyse_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
if (stat(filepath.c_str(), &filestat) < 0) {
Debug(1, "analyze file %s not found will try to stream from other", filepath.c_str());
@ -714,7 +716,9 @@ bool EventStream::sendFrame(Microseconds delta_us) {
filepath = "";
}
}
} else if ( !ffmpeg_input ) {
} else if (event_data->SaveJPEGs & 1) {
filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id);
} else if (!ffmpeg_input) {
Fatal("JPEGS not saved. zms is not capable of streaming jpegs from mp4 yet");
return false;
}
@ -795,7 +799,13 @@ bool EventStream::sendFrame(Microseconds delta_us) {
}
Image *send_image = prepareImage(image);
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
if (temp_img_buffer_size < send_image->Size()) {
Debug(1, "Resizing image buffer from %zu to %u",
temp_img_buffer_size, send_image->Size());
delete[] temp_img_buffer;
temp_img_buffer = new uint8_t[send_image->Size()];
temp_img_buffer_size = send_image->Size();
}
int img_buffer_size = 0;
uint8_t *img_buffer = temp_img_buffer;
@ -837,12 +847,13 @@ void EventStream::runStream() {
//checkInitialised();
if ( type == STREAM_JPEG )
if (type == STREAM_JPEG)
fputs("Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n", stdout);
if ( !event_data ) {
if (!event_data) {
sendTextFrame("No event data found");
exit(0);
zm_terminate = true;
return;
}
double fps = 1.0;
@ -851,13 +862,13 @@ void EventStream::runStream() {
}
updateFrameRate(fps);
start = std::chrono::system_clock::now();
start = std::chrono::steady_clock::now();
SystemTimePoint::duration last_frame_offset = Seconds(0);
SystemTimePoint::duration time_to_event = Seconds(0);
while ( !zm_terminate ) {
now = std::chrono::system_clock::now();
now = std::chrono::steady_clock::now();
Microseconds delta = Microseconds(0);
send_frame = false;
@ -880,7 +891,7 @@ void EventStream::runStream() {
if ( !paused ) {
// Figure out if we should send this frame
Debug(3, "not paused at cur_frame_id (%ld-1) mod frame_mod(%d)", curr_frame_id, frame_mod);
Debug(3, "not paused at cur_frame_id (%d-1) mod frame_mod(%d)", curr_frame_id, frame_mod);
// If we are streaming and this frame is due to be sent
// frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2
// so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc.
@ -904,7 +915,7 @@ void EventStream::runStream() {
// time_to_event > 0 means that we are not in the event
if (time_to_event > Seconds(0) and mode == MODE_ALL) {
SystemTimePoint::duration time_since_last_send = now - last_frame_sent;
TimePoint::duration time_since_last_send = now - last_frame_sent;
Debug(1, "Time since last send = %.2f s", FPSeconds(time_since_last_send).count());
if (time_since_last_send > Seconds(1)) {
char frame_text[64];
@ -976,13 +987,13 @@ void EventStream::runStream() {
// +/- 1? What if we are skipping frames?
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;
// sending the frame may have taken some time, so reload now
now = std::chrono::system_clock::now();
now = std::chrono::steady_clock::now();
// we incremented by replay_rate, so might have jumped past frame_count
if ( (mode == MODE_SINGLE) && (
(curr_frame_id < 1 )
||
((unsigned int)curr_frame_id >= event_data->frame_count)
(curr_frame_id >= event_data->frame_count)
)
) {
Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start");
@ -990,45 +1001,51 @@ void EventStream::runStream() {
// Have to reset start to now when replaying
start = now;
}
frame_data = &event_data->frames[curr_frame_id-1];
// frame_data->delta is the time since last frame as a float in seconds
// but what if we are skipping frames? We need the distance from the last frame sent
// Also, what about reverse? needs to be absolute value
if (curr_frame_id <= event_data->frame_count) {
frame_data = &event_data->frames[curr_frame_id-1];
// There are two ways to go about this, not sure which is correct.
// you can calculate the relationship between now and the start
// or calc the relationship from the last frame. I think from the start is better as it self-corrects
//
if (last_frame_offset != Seconds(0)) {
// We assume that we are going forward and the next frame is in the future.
delta = std::chrono::duration_cast<Microseconds>(frame_data->offset - (now - start));
// frame_data->delta is the time since last frame as a float in seconds
// but what if we are skipping frames? We need the distance from the last frame sent
// Also, what about reverse? needs to be absolute value
Debug(2, "New delta: now - start = %" PRIu64 " us offset %" PRIi64 " us- elapsed = %" PRIu64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(now - start).count()),
static_cast<int64>(std::chrono::duration_cast<Microseconds>(frame_data->offset).count()),
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
} else {
Debug(2, "No last frame_offset, no sleep");
delta = Seconds(0);
}
last_frame_offset = frame_data->offset;
// There are two ways to go about this, not sure which is correct.
// you can calculate the relationship between now and the start
// or calc the relationship from the last frame. I think from the start is better as it self-corrects
//
if (last_frame_offset != Seconds(0)) {
// We assume that we are going forward and the next frame is in the future.
delta = std::chrono::duration_cast<Microseconds>(frame_data->offset - (now - start));
if (send_frame && type != STREAM_MPEG) {
if (delta != Seconds(0)) {
if (delta > MAX_SLEEP) {
Debug(1, "Limiting sleep to %" PRIi64 " ms because calculated sleep is too long: %" PRIi64" us",
Debug(2, "New delta: now - start = %" PRIu64 " us offset %" PRIi64 " us- elapsed = %" PRIu64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(now - start).count()),
static_cast<int64>(std::chrono::duration_cast<Microseconds>(frame_data->offset).count()),
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
} else {
Debug(2, "No last frame_offset, no sleep");
delta = Seconds(0);
}
last_frame_offset = frame_data->offset;
if (send_frame && type != STREAM_MPEG) {
if (delta != Seconds(0)) {
if (delta > MAX_SLEEP) {
Debug(1, "Limiting sleep to %" PRIi64 " ms because calculated sleep is too long: %" PRIi64" us",
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(MAX_SLEEP).count()),
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
delta = MAX_SLEEP;
}
delta = MAX_SLEEP;
}
std::this_thread::sleep_for(delta);
Debug(3, "Done sleeping: %" PRIi64 " us",
std::this_thread::sleep_for(delta);
Debug(3, "Done sleeping: %" PRIi64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(delta).count()));
}
}
}
} // end if need to sleep
} else {
Debug(1, "invalid curr_frame_id %d !< %d", curr_frame_id, event_data->frame_count);
} // end if not at end of event
} else {
// Paused
delta = std::chrono::duration_cast<Microseconds>(FPSeconds(
ZM_RATE_BASE / ((base_fps ? base_fps : 1) * (replay_rate ? abs(replay_rate * 2) : 2))));
@ -1087,62 +1104,57 @@ void EventStream::runStream() {
} // end void EventStream::runStream()
bool EventStream::send_file(const std::string &filepath) {
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
int img_buffer_size = 0;
uint8_t *img_buffer = temp_img_buffer;
FILE *fdj = nullptr;
fdj = fopen(filepath.c_str(), "rb");
if ( !fdj ) {
FILE *fdj = fopen(filepath.c_str(), "rb");
if (!fdj) {
Error("Can't open %s: %s", filepath.c_str(), strerror(errno));
std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno));
return sendTextFrame(error_message.c_str());
}
#if HAVE_SENDFILE
static struct stat filestat;
if ( fstat(fileno(fdj), &filestat) < 0 ) {
if (fstat(fileno(fdj), &filestat) < 0) {
fclose(fdj); /* Close the file handle */
Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno));
return false;
}
if ( !filestat.st_size ) {
if (!filestat.st_size) {
fclose(fdj); /* Close the file handle */
Info("File size is zero. Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno));
Info("File size is zero. Unable to send raw frame %d: %s", curr_frame_id, strerror(errno));
return false;
}
if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size) ) {
if (0 > fprintf(stdout, "Content-Length: %jd\r\n\r\n", filestat.st_size)) {
fclose(fdj); /* Close the file handle */
Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno));
Info("Unable to send raw frame %d: %s", curr_frame_id, strerror(errno));
return false;
}
int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size);
if ( rc == (int)filestat.st_size ) {
ssize_t remaining = filestat.st_size;
while (remaining > 0) {
ssize_t rc = zm_sendfile(fileno(stdout), fileno(fdj), nullptr, remaining);
if (rc < 0) break;
if (rc > 0) {
remaining -= rc;
}
} // end while remaining
if (!remaining) {
// Success
fclose(fdj); /* Close the file handle */
return true;
}
Warning("Unable to send raw frame %ld: %s rc %d", curr_frame_id, strerror(errno), rc);
#endif
img_buffer_size = fread(img_buffer, 1, sizeof(temp_img_buffer), fdj);
fclose(fdj); /* Close the file handle */
if ( !img_buffer_size ) {
Info("Unable to read raw frame %ld: %s", curr_frame_id, strerror(errno));
return false;
}
return send_buffer(img_buffer, img_buffer_size);
}
Warning("Unable to send raw frame %d: %s %zu remaining",
curr_frame_id, strerror(errno), remaining);
return false;
} // end bool EventStream::send_file(const std::string &filepath)
bool EventStream::send_buffer(uint8_t* buffer, int size) {
if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) {
Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno));
Info("Unable to send raw frame %d: %s", curr_frame_id, strerror(errno));
return false;
}
int rc = fwrite(buffer, size, 1, stdout);
if ( 1 != rc ) {
Error("Unable to send raw frame %ld: %s %d", curr_frame_id, strerror(errno), rc);
Error("Unable to send raw frame %d: %s %d", curr_frame_id, strerror(errno), rc);
return false;
}
return true;
@ -1150,7 +1162,7 @@ bool EventStream::send_buffer(uint8_t* buffer, int size) {
void EventStream::setStreamStart(
uint64_t init_event_id,
unsigned int init_frame_id=0) {
int init_frame_id=0) {
loadInitialEventData(init_event_id, init_frame_id);
} // end void EventStream::setStreamStart(init_event_id,init_frame_id=0)

View File

@ -49,9 +49,9 @@ class EventStream : public StreamBase {
struct EventData {
uint64_t event_id;
unsigned int monitor_id;
unsigned long storage_id;
unsigned long frame_count; // Value of Frames column in Event
unsigned long last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events
unsigned int storage_id;
int frame_count; // Value of Frames column in Event
int last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events
SystemTimePoint start_time;
SystemTimePoint end_time;
Microseconds duration;
@ -73,16 +73,16 @@ class EventStream : public StreamBase {
StreamMode mode;
bool forceEventChange;
long curr_frame_id;
int curr_frame_id;
SystemTimePoint curr_stream_time;
bool send_frame;
SystemTimePoint start; // clock time when started the event
TimePoint start; // clock time when started the event
EventData *event_data;
protected:
bool loadEventData(uint64_t event_id);
bool loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id);
bool loadInitialEventData(uint64_t init_event_id, int init_frame_id);
bool loadInitialEventData(int monitor_id, SystemTimePoint event_time);
bool checkEventLoaded();
@ -118,7 +118,7 @@ class EventStream : public StreamBase {
ffmpeg_input = nullptr;
}
}
void setStreamStart(uint64_t init_event_id, unsigned int init_frame_id);
void setStreamStart(uint64_t init_event_id, int init_frame_id);
void setStreamStart(int monitor_id, time_t event_time);
void setStreamMode(StreamMode p_mode) { mode = p_mode; }
void runStream() override;

View File

@ -257,8 +257,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
Debug(1, "ids [0x%x]", st->id);
if (lang)
Debug(1, "language (%s)", lang->value);
Debug(1, "frames:%d, frame_size:%d stream timebase: %d/%d",
st->codec_info_nb_frames, codec->frame_size,
Debug(1, "frame_size:%d stream timebase: %d/%d",
codec->frame_size,
st->time_base.num, st->time_base.den
);

View File

@ -293,17 +293,16 @@ int FfmpegCamera::OpenFfmpeg() {
mFormatContext->interrupt_callback.opaque = this;
ret = avformat_open_input(&mFormatContext, mPath.c_str(), nullptr, &opts);
if ( ret != 0 )
{
Error("Unable to open input %s due to: %s", mPath.c_str(),
if (ret != 0) {
logPrintf(Logger::ERROR + monitor->Importance(),
"Unable to open input %s due to: %s", mPath.c_str(),
av_make_error_string(ret).c_str());
if ( mFormatContext ) {
if (mFormatContext) {
avformat_close_input(&mFormatContext);
mFormatContext = nullptr;
}
av_dict_free(&opts);
return -1;
}
AVDictionaryEntry *e = nullptr;
@ -458,6 +457,17 @@ int FfmpegCamera::OpenFfmpeg() {
#endif
} // end if hwaccel_name
// set codec to automatically determine how many threads suits best for the decoding job
mVideoCodecContext->thread_count = 0;
if (mVideoCodec->capabilities | AV_CODEC_CAP_FRAME_THREADS) {
mVideoCodecContext->thread_type = FF_THREAD_FRAME;
} else if (mVideoCodec->capabilities | AV_CODEC_CAP_SLICE_THREADS) {
mVideoCodecContext->thread_type = FF_THREAD_SLICE;
} else {
mVideoCodecContext->thread_count = 1; //don't use multithreading
}
ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts);
e = nullptr;

View File

@ -31,8 +31,7 @@ int FFmpeg_Input::Open(
const AVStream * audio_in_stream,
const AVCodecContext * audio_in_ctx
) {
video_stream_id = video_in_stream->index;
int max_stream_index = video_in_stream->index;
int max_stream_index = video_stream_id = video_in_stream->index;
if ( audio_in_stream ) {
max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index;

View File

@ -155,7 +155,7 @@ void FifoStream::runStream() {
}
while (!zm_terminate) {
now = std::chrono::system_clock::now();
now = std::chrono::steady_clock::now();
checkCommandQueue();
if (stream_type == MJPEG) {

View File

@ -28,7 +28,6 @@ class FifoStream : public StreamBase {
std::string stream_path;
int total_read;
int bytes_read;
unsigned int frame_count;
protected:
typedef enum { UNKNOWN, MJPEG, RAW } StreamType;
@ -39,9 +38,9 @@ class FifoStream : public StreamBase {
public:
FifoStream() :
StreamBase(),
total_read(0),
bytes_read(0),
frame_count(0),
stream_type(UNKNOWN)
{}

View File

@ -46,9 +46,9 @@ FileCamera::FileCamera(
p_hue,
p_colour,
p_capture,
p_record_audio)
p_record_audio),
path(p_path)
{
path = std::string(p_path);
if (capture) {
Initialise();
}

View File

@ -24,6 +24,7 @@
#include "zm_utils.h"
#include <algorithm>
#include <fcntl.h>
#include <mutex>
#include <sys/stat.h>
#include <unistd.h>
@ -80,9 +81,14 @@ imgbufcpy_fptr_t fptr_imgbufcpy;
/* Font */
static ZmFont font;
std::mutex jpeg_mutex;
void Image::update_function_pointers() {
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */
if ( pixels % 16 || pixels % 12 ) {
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements
* previous tests were %16 or %12 but that is incorrect. Should just be %4
*/
if (pixels %4) {
// have to use non-loop unrolled functions
delta8_rgb = &std_delta8_rgb;
delta8_bgr = &std_delta8_bgr;
@ -92,6 +98,7 @@ void Image::update_function_pointers() {
delta8_abgr = &std_delta8_abgr;
delta8_gray8 = &std_delta8_gray8;
blend = &std_blend;
Debug(1, "Using slow std functions because pixels %d mod 4=%d", pixels, pixels%4);
} else {
// Use either sse or neon, or loop unrolled version
delta8_rgb = fptr_delta8_rgb;
@ -114,22 +121,23 @@ Image::Image() :
delta8_argb(&std_delta8_argb),
delta8_abgr(&std_delta8_abgr),
delta8_gray8(&std_delta8_gray8),
blend(&std_blend)
blend(&std_blend),
width(0),
linesize(0),
height(0),
pixels(0),
colours(0),
padding(0),
size(0),
subpixelorder(0),
allocation(0),
buffer(nullptr),
buffertype(ZM_BUFTYPE_DONTFREE),
holdbuffer(0)
{
if ( !initialised )
if (!initialised)
Initialise();
width = 0;
linesize = 0;
height = 0;
padding = 0;
pixels = 0;
colours = 0;
subpixelorder = 0;
size = 0;
allocation = 0;
buffer = 0;
buffertype = ZM_BUFTYPE_DONTFREE;
holdbuffer = 0;
// Update blend to fast function determined by Initialise, I'm sure this can be improve.
blend = fptr_blend;
}
@ -158,15 +166,15 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer) {
buffer(p_buffer),
holdbuffer(0)
{
if (!initialised)
Initialise();
pixels = width * height;
linesize = p_width * p_colours;
size = linesize * height + padding;
buffer = nullptr;
holdbuffer = 0;
if (p_buffer) {
allocation = size;
buffertype = ZM_BUFTYPE_DONTFREE;
@ -174,7 +182,7 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
} else {
AllocImgBuffer(size);
}
if (!subpixelorder and colours>1) {
if (!subpixelorder and (colours>1)) {
// Default to RGBA when no subpixelorder is specified.
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
@ -214,25 +222,26 @@ Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_sub
update_function_pointers();
}
Image::Image(const AVFrame *frame) {
Image::Image(const AVFrame *frame) :
colours(ZM_COLOUR_RGB32),
padding(0),
subpixelorder(ZM_SUBPIX_ORDER_RGBA),
imagePixFormat(AV_PIX_FMT_RGBA),
buffer(0),
holdbuffer(0)
{
width = frame->width;
height = frame->height;
pixels = width*height;
zm_dump_video_frame(frame, "Image.Assign(frame)");
// FIXME
colours = ZM_COLOUR_RGB32;
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
imagePixFormat = AV_PIX_FMT_RGBA;
//(AVPixelFormat)frame->format;
//(AVPixelFormat)frame->format;
size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 32);
// av_image_get_linesize isn't aligned, so we have to do that.
linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 32);
padding = 0;
buffer = nullptr;
holdbuffer = 0;
AllocImgBuffer(size);
this->Assign(frame);
}
@ -1081,11 +1090,16 @@ bool Image::WriteJpeg(const std::string &filename,
const int &quality_override,
SystemTimePoint timestamp,
bool on_blocking_abort) const {
if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort);
}
// jpeg libs are not thread safe
std::unique_lock<std::mutex> lck(jpeg_mutex);
int quality = quality_override ? quality_override : config.jpeg_file_quality;
jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
@ -1155,7 +1169,7 @@ bool Image::WriteJpeg(const std::string &filename,
} else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
cinfo->in_color_space = JCS_EXT_XBGR;
} else {
Warning("Unknwon subpixelorder %d", subpixelorder);
Warning("Unknown subpixelorder %d", subpixelorder);
/* Assume RGBA */
cinfo->in_color_space = JCS_EXT_RGBX;
}
@ -1364,6 +1378,8 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr
return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override);
}
std::unique_lock<std::mutex> lck(jpeg_mutex);
int quality = quality_override ? quality_override : config.jpeg_stream_quality;
struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality];
@ -1677,15 +1693,15 @@ void Image::Overlay( const Image &image ) {
}
/* RGB32 compatible: complete */
void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) {
void Image::Overlay( const Image &image, const unsigned int lo_x, const unsigned int lo_y ) {
if ( !(width < image.width || height < image.height) ) {
Panic("Attempt to overlay image too big for destination, %dx%d > %dx%d",
image.width, image.height, width, height );
}
if ( !(width < (x+image.width) || height < (y+image.height)) ) {
if ( !(width < (lo_x+image.width) || height < (lo_y+image.height)) ) {
Panic("Attempt to overlay image outside of destination bounds, %dx%d @ %dx%d > %dx%d",
image.width, image.height, x, y, width, height );
image.width, image.height, lo_x, lo_y, width, height );
}
if ( !(colours == image.colours) ) {
@ -1693,10 +1709,8 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) {
colours, image.colours);
}
unsigned int lo_x = x;
unsigned int lo_y = y;
unsigned int hi_x = (x+image.width)-1;
unsigned int hi_y = (y+image.height-1);
unsigned int hi_x = (lo_x+image.width)-1;
unsigned int hi_y = (lo_y+image.height-1);
if ( colours == ZM_COLOUR_GRAY8 ) {
const uint8_t *psrc = image.buffer;
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
@ -1729,7 +1743,6 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) {
} // end void Image::Overlay( const Image &image, unsigned int x, unsigned int y )
void Image::Blend( const Image &image, int transparency ) {
uint8_t* new_buffer;
if ( !(
width == image.width && height == image.height
@ -1743,7 +1756,7 @@ void Image::Blend( const Image &image, int transparency ) {
if ( transparency <= 0 )
return;
new_buffer = AllocBuffer(size);
uint8_t* new_buffer = AllocBuffer(size);
#ifdef ZM_IMAGE_PROFILING
TimePoint start = std::chrono::steady_clock::now();
@ -2732,7 +2745,7 @@ void Image::Flip( bool leftright ) {
AssignDirect(width, height, colours, subpixelorder, flip_buffer, size, ZM_BUFTYPE_ZM);
}
void Image::Scale(unsigned int factor) {
void Image::Scale(const unsigned int factor) {
if ( !factor ) {
Error("Bogus scale factor %d found", factor);
return;
@ -2756,15 +2769,13 @@ void Image::Scale(unsigned int factor) {
unsigned int h_count = ZM_SCALE_BASE/2;
unsigned int last_h_index = 0;
unsigned int last_w_index = 0;
unsigned int h_index;
for ( unsigned int y = 0; y < height; y++ ) {
unsigned char *ps = &buffer[y*wc];
unsigned int w_count = ZM_SCALE_BASE/2;
unsigned int w_index;
last_w_index = 0;
for ( unsigned int x = 0; x < width; x++ ) {
w_count += factor;
w_index = w_count/ZM_SCALE_BASE;
unsigned int w_index = w_count/ZM_SCALE_BASE;
for (unsigned int f = last_w_index; f < w_index; f++ ) {
for ( unsigned int c = 0; c < colours; c++ ) {
*pd++ = *(ps+c);
@ -2774,7 +2785,7 @@ void Image::Scale(unsigned int factor) {
last_w_index = w_index;
}
h_count += factor;
h_index = h_count/ZM_SCALE_BASE;
unsigned int h_index = h_count/ZM_SCALE_BASE;
for ( unsigned int f = last_h_index+1; f < h_index; f++ ) {
memcpy(pd, pd-nwc, nwc);
pd += nwc;
@ -2786,17 +2797,14 @@ void Image::Scale(unsigned int factor) {
} else {
unsigned char *pd = scale_buffer;
unsigned int wc = width*colours;
unsigned int xstart = factor/2;
unsigned int ystart = factor/2;
unsigned int h_count = ystart;
unsigned int h_count = factor/2;
unsigned int last_h_index = 0;
unsigned int last_w_index = 0;
unsigned int h_index;
for ( unsigned int y = 0; y < height; y++ ) {
h_count += factor;
h_index = h_count/ZM_SCALE_BASE;
unsigned int h_index = h_count/ZM_SCALE_BASE;
if ( h_index > last_h_index ) {
unsigned int w_count = xstart;
unsigned int w_count = factor/2;
unsigned int w_index;
last_w_index = 0;
@ -2825,6 +2833,7 @@ void Image::Scale(unsigned int factor) {
void Image::Deinterlace_Discard() {
/* Simple deinterlacing. Copy the even lines into the odd lines */
// ICON: These can be drastically improved. But who cares?
if ( colours == ZM_COLOUR_GRAY8 ) {
const uint8_t *psrc;
@ -3107,9 +3116,9 @@ __attribute__((noinline,__target__("sse2")))
#endif
void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
static uint32_t divider = 0;
static uint32_t clearmask = 0;
static double current_blendpercent = 0.0;
static uint32_t clearmask = 0;
static uint32_t divider = 0;
if ( current_blendpercent != blendpercent ) {
/* Attempt to match the blending percent to one of the possible values */
@ -3310,10 +3319,10 @@ void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* r
__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;
static int8_t divider = 0;
if(current_blendpercent != blendpercent) {
if (current_blendpercent != blendpercent) {
/* Attempt to match the blending percent to one of the possible values */
if(blendpercent < 2.34375) {
// 1.5625% blending
@ -3393,6 +3402,7 @@ __attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const
}
__attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
Warning("Using slow std_blend");
double divide = blendpercent / 100.0;
double opacity = 1.0 - divide;
const uint8_t* const max_ptr = result + count;

View File

@ -145,11 +145,13 @@ class Image {
explicit Image(const AVFrame *frame);
~Image();
static void Initialise();
static void Deinitialise();
inline void DumpImgBuffer() {
DumpBuffer(buffer, buffertype);
if (buffertype != ZM_BUFTYPE_DONTFREE)
DumpBuffer(buffer, buffertype);
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = nullptr;
allocation = 0;

View File

@ -23,7 +23,7 @@ void bind_libvnc_symbols() {
libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL);
if (!libvnc_lib) {
Error("Error loading libvncclient: %s", dlerror());
Error("Error loading libvncclient.so: %s", dlerror());
return;
}
@ -135,11 +135,6 @@ VncCamera::VncCamera(
}
VncCamera::~VncCamera() {
if (capture and mRfb) {
if (mRfb->frameBuffer)
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
}
if (libvnc_lib) {
dlclose(libvnc_lib);
libvnc_lib = nullptr;
@ -253,6 +248,12 @@ int VncCamera::PostCapture() {
}
int VncCamera::Close() {
if (capture and mRfb) {
if (mRfb->frameBuffer)
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
mRfb = nullptr;
}
return 1;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@
#include "zm_define.h"
#include "zm_camera.h"
#include "zm_analysis_thread.h"
#include "zm_poll_thread.h"
#include "zm_decoder_thread.h"
#include "zm_event.h"
#include "zm_fifo.h"
@ -33,6 +34,13 @@
#include <memory>
#include <sys/time.h>
#include <vector>
#include <curl/curl.h>
#ifdef WITH_GSOAP
#include "soapPullPointSubscriptionBindingProxy.h"
#include "plugin/wsseapi.h"
#include <openssl/err.h>
#endif
class Group;
@ -40,6 +48,7 @@ class Group;
#define MOTION_CAUSE "Motion"
#define LINKED_CAUSE "Linked"
//
// This is the main class for monitors. Each monitor is associated
// with a camera and is effectively a collector for events.
@ -69,7 +78,7 @@ public:
FILE,
FFMPEG,
LIBVLC,
CURL,
LIBCURL,
NVSOCKET,
VNC,
} CameraType;
@ -84,7 +93,7 @@ public:
} Orientation;
typedef enum {
DEINTERLACE_DISABLED = 0x00000000,
DEINTERLACE_DISABLED = 0x00000000,
DEINTERLACE_FOUR_FIELD_SOFT = 0x00001E04,
DEINTERLACE_FOUR_FIELD_MEDIUM = 0x00001404,
DEINTERLACE_FOUR_FIELD_HARD = 0x00000A04,
@ -145,12 +154,12 @@ protected:
uint32_t last_frame_score; /* +60 */
uint32_t audio_frequency; /* +64 */
uint32_t audio_channels; /* +68 */
/*
/*
** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple
** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple
** of 8. Add or delete epadding's to achieve this.
*/
*/
union { /* +72 */
time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */
uint64_t extrapad1;
@ -247,7 +256,51 @@ protected:
bool hasAlarmed();
};
class AmcrestAPI {
protected:
Monitor *parent;
std::string amcrest_response;
CURLM *curl_multi = nullptr;
CURL *Amcrest_handle = nullptr;
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
public:
AmcrestAPI( Monitor *parent_);
~AmcrestAPI();
int API_Connect();
void WaitForMessage();
bool Amcrest_Alarmed;
int start_Amcrest();
};
class JanusManager {
protected:
Monitor *parent;
CURL *curl = nullptr;
//helper class for CURL
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
bool Janus_Healthy;
std::string janus_session;
std::string janus_handle;
std::string janus_endpoint;
std::string stream_key;
std::string rtsp_username;
std::string rtsp_password;
std::string rtsp_path;
public:
JanusManager(Monitor *parent_);
~JanusManager();
int add_to_janus();
int check_janus();
int remove_from_janus();
int get_janus_session();
int get_janus_handle();
int get_janus_plugin();
std::string get_stream_key();
};
// These are read from the DB and thereafter remain unchanged
unsigned int id;
std::string name;
@ -257,6 +310,8 @@ protected:
Function function; // What the monitor is doing
bool enabled; // Whether the monitor is enabled or asleep
bool decoding_enabled; // Whether the monitor will decode h264/h265 packets
bool janus_enabled; // Whether we set the h264/h265 stream up on janus
bool janus_audio_enabled; // Whether we tell Janus to try to include audio.
std::string protocol;
std::string method;
@ -268,6 +323,13 @@ protected:
std::string path;
std::string second_path;
std::string onvif_url;
std::string onvif_username;
std::string onvif_password;
std::string onvif_options;
bool onvif_event_listener;
bool use_Amcrest_API;
std::string device;
int palette;
int channel;
@ -351,7 +413,6 @@ protected:
int first_alarm_count;
int last_alarm_count;
bool last_signal;
int last_section_mod;
int buffer_count;
State state;
SystemTimePoint start_time;
@ -390,6 +451,7 @@ protected:
VideoStore *videoStore;
PacketQueue packetqueue;
std::unique_ptr<PollThread> Poller;
packetqueue_iterator *analysis_it;
std::unique_ptr<AnalysisThread> analysis_thread;
packetqueue_iterator *decoder_it;
@ -404,6 +466,8 @@ protected:
int n_linked_monitors;
MonitorLink **linked_monitors;
std::string event_start_command;
std::string event_end_command;
std::vector<Group *> groups;
@ -414,6 +478,25 @@ protected:
std::string diag_path_ref;
std::string diag_path_delta;
//ONVIF
bool Poll_Trigger_State;
bool Event_Poller_Healthy;
bool Event_Poller_Closes_Event;
JanusManager *Janus_Manager;
AmcrestAPI *Amcrest_Manager;
#ifdef WITH_GSOAP
struct soap *soap = nullptr;
_tev__CreatePullPointSubscription request;
_tev__CreatePullPointSubscriptionResponse response;
_tev__PullMessages tev__PullMessages;
_tev__PullMessagesResponse tev__PullMessagesResponse;
PullPointSubscriptionBindingProxy proxyEvent;
void set_credentials(struct soap *soap);
#endif
// Used in check signal
uint8_t red_val;
uint8_t green_val;
@ -470,6 +553,19 @@ public:
inline bool DecodingEnabled() const {
return decoding_enabled;
}
bool JanusEnabled() {
return janus_enabled;
}
bool JanusAudioEnabled() {
return janus_audio_enabled;
}
bool OnvifEnabled() {
return onvif_event_listener;
}
int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error.
bool EventPollerHealthy() {
return Event_Poller_Healthy;
}
inline const char *EventPrefix() const { return event_prefix.c_str(); }
inline bool Ready() const {
if ( image_count >= ready_count ) {
@ -518,7 +614,7 @@ public:
void SetVideoWriterStartTime(SystemTimePoint t) {
video_store_data->recording = zm::chrono::duration_cast<timeval>(t.time_since_epoch());
}
unsigned int GetPreEventCount() const { return pre_event_count; };
int32_t GetImageBufferCount() const { return image_buffer_count; };
State GetState() const { return (State)shared_data->state; }
@ -533,6 +629,12 @@ public:
std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; };
std::string GetRTSPStreamName() const { return rtsp_streamname; };
const std::string &getONVIF_URL() const { return onvif_url; };
const std::string &getONVIF_Username() const { return onvif_username; };
const std::string &getONVIF_Password() const { return onvif_password; };
const std::string &getONVIF_Options() const { return onvif_options; };
Image *GetAlarmImage();
int GetImage(int32_t index=-1, int scale=100);
ZMPacket *getSnapshot( int index=-1 ) const;
SystemTimePoint GetTimestamp(int index = -1) const;
@ -589,8 +691,13 @@ public:
bool CheckSignal( const Image *image );
bool Analyse();
bool Decode();
bool Poll();
void DumpImage( Image *dump_image ) const;
void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const;
Event *openEvent(
const std::shared_ptr<ZMPacket> &snap,
const std::string &cause,
const Event::StringSetMap &noteSetMap);
void closeEvent();
void Reload();

126
src/zm_monitor_amcrest.cpp Normal file
View File

@ -0,0 +1,126 @@
//
// ZoneMinder Monitor::AmcrestAPI Class Implementation, $Date$, $Revision$
// Copyright (C) 2022 Jonathan Bennett
//
// 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.
//
#include "zm_monitor.h"
Monitor::AmcrestAPI::AmcrestAPI(Monitor *parent_) {
parent = parent_;
curl_multi = curl_multi_init();
start_Amcrest();
}
Monitor::AmcrestAPI::~AmcrestAPI() {
if (Amcrest_handle != nullptr) { //potentially clean up the old handle
curl_multi_remove_handle(curl_multi, Amcrest_handle);
curl_easy_cleanup(Amcrest_handle);
}
if (curl_multi != nullptr) curl_multi_cleanup(curl_multi);
}
int Monitor::AmcrestAPI::start_Amcrest() {
//init the transfer and start it in multi-handle
int running_handles;
long response_code;
struct CURLMsg *m;
CURLMcode curl_error;
if (Amcrest_handle != nullptr) { //potentially clean up the old handle
curl_multi_remove_handle(curl_multi, Amcrest_handle);
curl_easy_cleanup(Amcrest_handle);
}
std::string full_url = parent->onvif_url;
if (full_url.back() != '/') full_url += '/';
full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]";
Amcrest_handle = curl_easy_init();
if (!Amcrest_handle){
Warning("Handle is null!");
return -1;
}
curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str());
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response);
curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, parent->onvif_username.c_str());
curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, parent->onvif_password.c_str());
curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle);
if (curl_error != CURLM_OK) {
Warning("error of %i", curl_error);
}
curl_error = curl_multi_perform(curl_multi, &running_handles);
if (curl_error == CURLM_OK) {
curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code);
int msgq = 0;
m = curl_multi_info_read(curl_multi, &msgq);
if (m && (m->msg == CURLMSG_DONE)) {
Warning("Libcurl exited Early: %i", m->data.result);
}
curl_multi_wait(curl_multi, NULL, 0, 300, NULL);
curl_error = curl_multi_perform(curl_multi, &running_handles);
}
if ((curl_error == CURLM_OK) && (running_handles > 0)) {
parent->Event_Poller_Healthy = TRUE;
} else {
Warning("Response: %s", amcrest_response.c_str());
Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str());
curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code);
Warning("Response code: %lu", response_code);
}
return 0;
}
void Monitor::AmcrestAPI::WaitForMessage() {
int open_handles;
int transfers;
curl_multi_perform(curl_multi, &open_handles);
if (open_handles == 0) {
start_Amcrest(); //http transfer ended, need to restart.
} else {
curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event.
if (transfers > 0) { //have data to deal with
curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response
if (amcrest_response.find("action=Start") != std::string::npos) {
//Event Start
Debug(1,"Triggered on ONVIF");
if (!parent->Poll_Trigger_State) {
Debug(1,"Triggered Event");
parent->Poll_Trigger_State = TRUE;
}
} else if (amcrest_response.find("action=Stop") != std::string::npos){
Debug(1, "Triggered off ONVIF");
parent->Poll_Trigger_State = FALSE;
if (!parent->Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them.
parent->Event_Poller_Closes_Event = TRUE;
Debug(1,"Setting ClosesEvent");
}
}
amcrest_response.clear(); //We've dealt with the message, need to clear the queue
}
}
return;
}
size_t Monitor::AmcrestAPI::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}

316
src/zm_monitor_janus.cpp Normal file
View File

@ -0,0 +1,316 @@
//
// ZoneMinder Monitor::JanusManager Class Implementation, $Date$, $Revision$
// Copyright (C) 2022 Jonathan Bennett
//
// 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.
//
#include "zm_monitor.h"
Monitor::JanusManager::JanusManager(Monitor *parent_) { //constructor takes care of init and calls add_to
std::string response;
std::size_t pos;
parent = parent_;
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
janus_endpoint = config.janus_path; //TODO: strip trailing /
} else {
janus_endpoint = "127.0.0.1:8088/janus";
}
if (janus_endpoint.back() == '/') janus_endpoint.pop_back(); //remove the trailing slash if present
std::size_t pos2 = parent->path.find("@", pos);
if (pos2 != std::string::npos) { //If we find an @ symbol, we have a username/password. Otherwise, passwordless login.
std::size_t pos = parent->path.find(":", 7); //Search for the colon, but only after the RTSP:// text.
if (pos == std::string::npos) throw std::runtime_error("Cannot Parse URL for Janus."); //Looks like an invalid url
rtsp_username = parent->path.substr(7, pos-7);
rtsp_password = parent->path.substr(pos+1, pos2 - pos - 1);
rtsp_path = "RTSP://";
rtsp_path += parent->path.substr(pos2 + 1);
} else {
rtsp_username = "";
rtsp_password = "";
rtsp_path = parent->path;
}
}
Monitor::JanusManager::~JanusManager() {
if (janus_session.empty()) get_janus_session();
if (janus_handle.empty()) get_janus_handle();
std::string response;
std::string endpoint;
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
//std::size_t pos;
CURLcode res;
curl = curl_easy_init();
if(!curl) return;
endpoint = janus_endpoint;
endpoint += "/";
endpoint += janus_session;
endpoint += "/";
endpoint += janus_handle;
//Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"destroy\", \"admin_key\" : \"";
postData += config.janus_secret;
postData += "\", \"id\" : ";
postData += std::to_string(parent->id);
postData += "}}";
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return;
}
Debug(1, "Removed stream from Janus: %s", response.c_str());
curl_easy_cleanup(curl);
return;
}
int Monitor::JanusManager::check_janus() {
if (janus_session.empty()) get_janus_session();
if (janus_handle.empty()) get_janus_handle();
std::string response;
std::string endpoint = janus_endpoint;
std::string postData;
//std::size_t pos;
CURLcode res;
curl = curl_easy_init();
if(!curl) return -1;
endpoint += "/";
endpoint += janus_session;
endpoint += "/";
endpoint += janus_handle;
//Assemble our actual request
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"info\", \"id\" : ";
postData += std::to_string(parent->id);
postData += "}}";
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session
Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
janus_session = "";
janus_handle = "";
return -1;
}
curl_easy_cleanup(curl);
Debug(1, "Queried for stream status: %s", response.c_str());
if (response.find("\"janus\": \"error\"") != std::string::npos) {
if (response.find("No such session") != std::string::npos) {
Warning("Janus Session timed out");
janus_session = "";
return -2;
} else if (response.find("No such handle") != std::string::npos) {
Warning("Janus Handle timed out");
janus_handle = "";
return -2;
}
} else if (response.find("No such mountpoint") != std::string::npos) {
Warning("Mountpoint Missing");
return 0;
}
return 1;
}
int Monitor::JanusManager::add_to_janus() {
if (janus_session.empty()) get_janus_session();
if (janus_handle.empty()) get_janus_handle();
std::string response;
std::string endpoint = janus_endpoint;
CURLcode res;
curl = curl_easy_init();
if (!curl) {
Error("Failed to init curl");
return -1;
}
endpoint += "/";
endpoint += janus_session;
endpoint += "/";
endpoint += janus_handle;
//Assemble our actual request
std::string postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
postData += "\"request\" : \"create\", \"admin_key\" : \"";
postData += config.janus_secret;
postData += "\", \"type\" : \"rtsp\", ";
postData += "\"url\" : \"";
postData += rtsp_path;
if (rtsp_username != "") {
postData += "\", \"rtsp_user\" : \"";
postData += rtsp_username;
postData += "\", \"rtsp_pwd\" : \"";
postData += rtsp_password;
}
postData += "\", \"id\" : ";
postData += std::to_string(parent->id);
if (parent->janus_audio_enabled) postData += ", \"audio\" : true";
postData += ", \"video\" : true}}";
Warning("Sending %s to %s", postData.c_str(), endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
Error("Failed to curl_easy_perform adding rtsp stream");
curl_easy_cleanup(curl);
return -1;
}
if (response.find("\"janus\": \"error\"") != std::string::npos) {
if (response.find("No such session") != std::string::npos) {
Warning("Janus Session timed out");
janus_session = "";
return -2;
} else if (response.find("No such handle") != std::string::npos) {
Warning("Janus Handle timed out");
janus_handle = "";
return -2;
}
}
//scan for missing session or handle id "No such session" "no such handle"
Debug(1,"Added stream to Janus: %s", response.c_str());
curl_easy_cleanup(curl);
return 0;
}
size_t Monitor::JanusManager::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
/*
void Monitor::JanusManager::generateKey()
{
const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
std::random_device random_device;
std::mt19937 generator(random_device());
std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1);
std::string random_string;
for (std::size_t i = 0; i < 16; ++i)
{
random_string += CHARACTERS[distribution(generator)];
}
stream_key = random_string;
}
*/
int Monitor::JanusManager::get_janus_session() {
janus_session = "";
std::string endpoint = janus_endpoint;
std::string response;
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
std::size_t pos;
CURLcode res;
curl = curl_easy_init();
if(!curl) return -1;
curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
pos = response.find("\"id\": ");
if (pos == std::string::npos)
{
curl_easy_cleanup(curl);
return -1;
}
janus_session = response.substr(pos + 6, 16);
curl_easy_cleanup(curl);
return 1;
} //get_janus_session
int Monitor::JanusManager::get_janus_handle() {
std::string response = "";
std::string endpoint = janus_endpoint;
std::size_t pos;
CURLcode res;
curl = curl_easy_init();
if(!curl) return -1;
endpoint += "/";
endpoint += janus_session;
std::string postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}";
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
pos = response.find("\"id\": ");
if (pos == std::string::npos)
{
curl_easy_cleanup(curl);
return -1;
}
janus_handle = response.substr(pos + 6, 16);
curl_easy_cleanup(curl);
return 1;
} //get_janus_handle

View File

@ -0,0 +1,199 @@
//
// ZoneMinder Monitor Class 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.
//
#include "zm_monitor.h"
#include <sys/stat.h>
#if ZM_MEM_MAPPED
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#else // ZM_MEM_MAPPED
#include <sys/ipc.h>
#include <sys/shm.h>
#endif // ZM_MEM_MAPPED
Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) :
id(p_id),
shared_data(nullptr),
trigger_data(nullptr),
video_store_data(nullptr)
{
strncpy(name, p_name, sizeof(name)-1);
#if ZM_MEM_MAPPED
map_fd = -1;
mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id);
#else // ZM_MEM_MAPPED
shm_id = 0;
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
last_event_id = 0;
last_state = IDLE;
last_connect_time = 0;
connected = false;
}
Monitor::MonitorLink::~MonitorLink() {
disconnect();
}
bool Monitor::MonitorLink::connect() {
SystemTimePoint now = std::chrono::system_clock::now();
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) {
last_connect_time = std::chrono::system_clock::to_time_t(now);
mem_size = sizeof(SharedData) + sizeof(TriggerData);
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
#if ZM_MEM_MAPPED
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
if (map_fd < 0) {
Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
}
while (map_fd <= 2) {
int new_map_fd = dup(map_fd);
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
close(map_fd);
map_fd = new_map_fd;
}
struct stat map_stat;
if (fstat(map_fd, &map_stat) < 0) {
Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
}
if (map_stat.st_size == 0) {
Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
} else if (map_stat.st_size < mem_size) {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
disconnect();
return false;
}
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
if (mem_ptr == MAP_FAILED) {
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(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) {
Debug(3, "Can't shmget link memory: %s", strerror(errno));
connected = false;
return false;
}
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
if ((int)mem_ptr == -1) {
Debug(3, "Can't shmat link memory: %s", strerror(errno));
connected = false;
return false;
}
#endif // ZM_MEM_MAPPED
shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
if (!shared_data->valid) {
Debug(3, "Linked memory not initialised by capture daemon");
disconnect();
return false;
}
last_state = shared_data->state;
last_event_id = shared_data->last_event_id;
connected = true;
return true;
}
return false;
} // end bool Monitor::MonitorLink::connect()
bool Monitor::MonitorLink::disconnect() {
if (connected) {
connected = false;
#if ZM_MEM_MAPPED
if (mem_ptr > (void *)0) {
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
}
if (map_fd >= 0)
close(map_fd);
map_fd = -1;
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
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) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
}
if (shmdt(mem_ptr) < 0) {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
}
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
}
return true;
}
bool Monitor::MonitorLink::isAlarmed() {
if (!connected) {
return false;
}
return( shared_data->state == ALARM );
}
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) {
return true;
}
last_event_id = shared_data->last_event_id;
return false;
}

View File

@ -134,6 +134,18 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break;
}
break;
case CMD_MAXFPS :
{
double int_part = ((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];
double dec_part = ((unsigned char) msg->msg_data[5] << 24) | ((unsigned char) msg->msg_data[6] << 16)
| ((unsigned char) msg->msg_data[7] << 8) | (unsigned char) msg->msg_data[8];
maxfps = (int_part + dec_part / 1000000.0);
Debug(1, "Got MAXFPS %f", maxfps);
break;
}
case CMD_SLOWFWD :
Debug(1, "Got SLOW FWD command");
paused = true;
@ -231,6 +243,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
Info("User initiated exit - CMD_QUIT");
zm_terminate = true;
break;
case CMD_ANALYZE_ON :
frame_type = FRAME_ANALYSIS;
Debug(1, "ANALYSIS on");
break;
case CMD_ANALYZE_OFF :
frame_type = FRAME_NORMAL;
Debug(1, "ANALYSIS off");
break;
case CMD_QUERY :
Debug(1, "Got QUERY command, sending STATUS");
break;
@ -268,7 +288,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} else {
FPSeconds elapsed = now - last_fps_update;
if (elapsed.count()) {
actual_fps = (frame_count - last_frame_count) / elapsed.count();
actual_fps = (actual_fps + (frame_count - last_frame_count) / elapsed.count())/2;
last_frame_count = frame_count;
last_fps_update = now;
}
@ -288,9 +308,9 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.delayed = delayed;
status_data.paused = paused;
status_data.rate = replay_rate;
status_data.delay = FPSeconds(now - last_frame_timestamp).count();
status_data.delay = FPSeconds(now - last_frame_sent).count();
status_data.zoom = zoom;
Debug(2, "fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d",
Debug(2, "viewing fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d",
status_data.fps,
status_data.capture_fps,
status_data.analysis_fps,
@ -359,14 +379,15 @@ bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint times
fputs("\r\n", stdout);
fflush(stdout);
TimePoint send_end_time = std::chrono::steady_clock::now();
TimePoint::duration frame_send_time = send_end_time - send_start_time;
if (maxfps > 0.0) {
TimePoint send_end_time = std::chrono::steady_clock::now();
TimePoint::duration frame_send_time = send_end_time - send_start_time;
if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) {
maxfps /= 2;
Info("Frame send time %" PRIi64 " ms too slow, throttling maxfps to %.2f",
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(frame_send_time).count()),
maxfps);
if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) {
Info("Frame send time %" PRIi64 " ms too slow, throttling maxfps to %.2f",
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(frame_send_time).count()),
maxfps);
}
}
last_frame_sent = now;
@ -377,12 +398,15 @@ bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint times
}
bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
Image *send_image = prepareImage(image);
if (!config.timestamp_on_capture) {
monitor->TimestampImage(send_image, timestamp);
monitor->TimestampImage(image, timestamp);
}
Image *send_image = prepareImage(image);
fputs("--" BOUNDARY "\r\n", stdout);
// Calculate how long it takes to actually send the frame
TimePoint send_start_time = std::chrono::steady_clock::now();
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());
@ -398,14 +422,17 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
/* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.count());
} else {
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
if (temp_img_buffer_size < send_image->Size()) {
Debug(1, "Resizing image buffer from %zu to %u",
temp_img_buffer_size, send_image->Size());
delete[] temp_img_buffer;
temp_img_buffer = new uint8_t[send_image->Size()];
temp_img_buffer_size = send_image->Size();
}
int img_buffer_size = 0;
unsigned char *img_buffer = temp_img_buffer;
// Calculate how long it takes to actually send the frame
TimePoint send_start_time = std::chrono::steady_clock::now();
switch ( type ) {
case STREAM_JPEG :
send_image->EncodeJpeg(img_buffer, &img_buffer_size);
@ -446,19 +473,23 @@ bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) {
fputs("\r\n", stdout);
fflush(stdout);
TimePoint send_end_time = std::chrono::steady_clock::now();
TimePoint::duration frame_send_time = send_end_time - send_start_time;
if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) {
maxfps /= 1.5;
Warning("Frame send time %" PRIi64 " msec too slow, throttling maxfps to %.2f",
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(frame_send_time).count()),
maxfps);
}
} // Not mpeg
last_frame_sent = now;
last_frame_sent = std::chrono::steady_clock::now();
if (maxfps > 0.0) {
TimePoint::duration frame_send_time = last_frame_sent - send_start_time;
TimePoint::duration maxfps_milliseconds = Milliseconds(lround(Milliseconds::period::den / maxfps));
if (frame_send_time > maxfps_milliseconds) {
//maxfps /= 1.5;
Warning("Frame send time %" PRIi64 " msec too slow (> %" PRIi64 ", throttling maxfps to %.3f",
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(frame_send_time).count()),
static_cast<int64>(std::chrono::duration_cast<Milliseconds>(maxfps_milliseconds).count()),
maxfps);
}
}
return true;
}
} // end bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp)
void MonitorStream::runStream() {
if (type == STREAM_SINGLE) {
@ -501,7 +532,8 @@ void MonitorStream::runStream() {
// point to end which is theoretically not a valid value because all indexes are % image_buffer_count
int32_t last_read_index = monitor->image_buffer_count;
SystemTimePoint stream_start_time = std::chrono::system_clock::now();
TimePoint stream_start_time = std::chrono::steady_clock::now();
when_to_send_next_frame = stream_start_time; // initialize it to now so that we spit out a frame immediately
frame_count = 0;
@ -570,7 +602,7 @@ void MonitorStream::runStream() {
break;
}
now = std::chrono::system_clock::now();
now = std::chrono::steady_clock::now();
bool was_paused = paused;
bool got_command = false; // commands like zoom should output a frame even if paused
@ -617,7 +649,7 @@ void MonitorStream::runStream() {
temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count);
} else {
FPSeconds expected_delta_time = ((FPSeconds(swap_image->timestamp - last_frame_timestamp)) * ZM_RATE_BASE) / replay_rate;
SystemTimePoint::duration actual_delta_time = now - last_frame_sent;
TimePoint::duration actual_delta_time = now - last_frame_sent;
// If the next frame is due
if (actual_delta_time > expected_delta_time) {
@ -683,7 +715,8 @@ void MonitorStream::runStream() {
if (last_read_index != monitor->shared_data->last_write_index) {
// have a new image to send
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
if ((frame_mod == 1) || ((frame_count%frame_mod) == 0)) {
//if ((frame_mod == 1) || ((frame_count%frame_mod) == 0)) {
if ( now >= when_to_send_next_frame ) {
if (!paused && !delayed) {
last_read_index = monitor->shared_data->last_write_index;
Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
@ -693,9 +726,22 @@ void MonitorStream::runStream() {
// Perhaps we should use NOW instead.
last_frame_timestamp =
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index]));
Image *image = monitor->image_buffer[index];
if (!sendFrame(image, last_frame_timestamp)) {
Image *send_image = nullptr;
if ((frame_type == FRAME_ANALYSIS) &&
(monitor->GetFunction() == Monitor::MOCORD || monitor->GetFunction() == Monitor::MODECT)) {
Debug(1, "Sending analysis image");
send_image = monitor->GetAlarmImage();
if (!send_image) {
Debug(1, "Falling back");
send_image = monitor->image_buffer[index];
}
} else {
Debug(1, "Sending regular image index %d", index);
send_image = monitor->image_buffer[index];
}
if (!sendFrame(send_image, last_frame_timestamp)) {
Debug(2, "sendFrame failed, quiting.");
zm_terminate = true;
break;
@ -704,7 +750,7 @@ void MonitorStream::runStream() {
if (frame_count == 0) {
// Chrome will not display the first frame until it receives another.
// Firefox is fine. So just send the first frame twice.
if (!sendFrame(image, last_frame_timestamp)) {
if (!sendFrame(send_image, last_frame_timestamp)) {
Debug(2, "sendFrame failed, quiting.");
zm_terminate = true;
break;
@ -726,7 +772,7 @@ void MonitorStream::runStream() {
frame_count++;
frame_count++;
} else {
SystemTimePoint::duration actual_delta_time = now - last_frame_sent;
TimePoint::duration actual_delta_time = now - last_frame_sent;
if (actual_delta_time > Seconds(5)) {
if (paused_image) {
// Send keepalive
@ -742,9 +788,9 @@ void MonitorStream::runStream() {
} // end if actual_delta_time > 5
} // end if change in zoom
} // end if paused or not
} else {
frame_count++;
} // end if should send frame
//} else {
//frame_count++;
} // end if should send frame now > when_to_send_next_frame
if (buffered_playback && !paused) {
if (monitor->shared_data->valid) {
@ -775,17 +821,42 @@ void MonitorStream::runStream() {
}
} // end if buffered playback
} else {
Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
Debug(3, "Waiting for capture last_write_index=%u == last_read_index=%u",
monitor->shared_data->last_write_index,
last_read_index);
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
FPSeconds sleep_time =
FPSeconds(ZM_RATE_BASE / ((base_fps ? base_fps : 1) * (replay_rate ? abs(replay_rate * 2) : 2)));
FPSeconds sleep_time;
if (now >= when_to_send_next_frame) {
// sent a frame, so update
double capture_fps = monitor->GetFPS();
double fps = ((maxfps > 0.0) && (capture_fps > maxfps)) ? maxfps : capture_fps;
double sleep_time_seconds = (1 / ((fps ? fps : 1))) // 1 second / fps
* (replay_rate ? abs(replay_rate)/ZM_RATE_BASE : 1); // replay_rate is 100 for 1x
Debug(3, "Using %f for maxfps. capture_fps: %f maxfps %f * replay_rate: %d = %f", fps, capture_fps, maxfps, replay_rate, sleep_time_seconds);
sleep_time = FPSeconds(sleep_time_seconds);
if (when_to_send_next_frame > now)
sleep_time -= when_to_send_next_frame - now;
when_to_send_next_frame = now + std::chrono::duration_cast<Microseconds>(sleep_time);
if (last_frame_sent > now) {
FPSeconds elapsed = last_frame_sent - now;
if (sleep_time > elapsed) {
sleep_time -= elapsed;
}
}
} else {
sleep_time = when_to_send_next_frame - now;
}
if (sleep_time > MonitorStream::MAX_SLEEP) {
Debug(3, "Sleeping for MAX_SLEEP_USEC instead of %" PRIi64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(sleep_time).count()));
// Shouldn't sleep for long because we need to check command queue, etc.
sleep_time = MonitorStream::MAX_SLEEP;
Debug(3, "Sleeping for MAX_SLEEP_USEC %" PRIi64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(sleep_time).count()));
} else {
Debug(3, "Sleeping for %" PRIi64 " us",
static_cast<int64>(std::chrono::duration_cast<Microseconds>(sleep_time).count()));
@ -797,13 +868,6 @@ void MonitorStream::runStream() {
static_cast<int64>(std::chrono::duration_cast<Microseconds>(ttl).count()));
break;
}
if (last_frame_sent.time_since_epoch() == Seconds(0)) {
// If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value.
last_frame_sent = now;
Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)",
frame_mod, frame_count);
}
} // end while ! zm_terminate
if (buffered_playback) {
@ -854,16 +918,16 @@ void MonitorStream::SingleImage(int scale) {
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index);
Image *snap_image = monitor->image_buffer[index];
if (!config.timestamp_on_capture) {
monitor->TimestampImage(snap_image,
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])));
}
if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign(*snap_image);
scaled_image.Scale(scale);
snap_image = &scaled_image;
}
if (!config.timestamp_on_capture) {
monitor->TimestampImage(snap_image,
SystemTimePoint(zm::chrono::duration_cast<Microseconds>(monitor->shared_timestamps[index])));
}
snap_image->EncodeJpeg(img_buffer, &img_buffer_size);
fprintf(stdout,

View File

@ -1,17 +1,17 @@
/*
* ZoneMinder MPEG class 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.
@ -42,19 +42,26 @@ void VideoStream::SetupFormat( ) {
ofc = nullptr;
avformat_alloc_output_context2(&ofc, nullptr, format, filename);
if ( !ofc ) {
if (!ofc) {
Fatal("avformat_alloc_..._context failed");
}
of = ofc->oformat;
Debug(1, "Using output format: %s (%s)", of->name, of->long_name);
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 ) {
int VideoStream::SetupCodec(
int colours,
int subpixelorder,
int width,
int height,
int bitrate,
double frame_rate
) {
/* ffmpeg format matching */
switch ( colours ) {
switch (colours) {
case ZM_COLOUR_RGB24:
if ( subpixelorder == ZM_SUBPIX_ORDER_BGR ) {
if (subpixelorder == ZM_SUBPIX_ORDER_BGR) {
/* BGR subpixel order */
pf = AV_PIX_FMT_BGR24;
} else {
@ -63,13 +70,13 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei
}
break;
case ZM_COLOUR_RGB32:
if ( subpixelorder == ZM_SUBPIX_ORDER_ARGB ) {
if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
/* ARGB subpixel order */
pf = AV_PIX_FMT_ARGB;
} else if ( subpixelorder == ZM_SUBPIX_ORDER_ABGR ) {
} else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
/* ABGR subpixel order */
pf = AV_PIX_FMT_ABGR;
} else if ( subpixelorder == ZM_SUBPIX_ORDER_BGRA ) {
} else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
/* BGRA subpixel order */
pf = AV_PIX_FMT_BGRA;
} else {
@ -85,22 +92,22 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei
break;
}
if ( strcmp("rtp", of->name) == 0 ) {
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;
Debug(1,"Setting packet_size to %d", ofc->packet_size);
if ( of->video_codec == AV_CODEC_ID_NONE ) {
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 ) {
if (codec_name) {
AVCodec *a = avcodec_find_encoder_by_name(codec_name);
if ( a ) {
if (a) {
codec_id = a->id;
Debug(1, "Using codec \"%s\"", codec_name);
} else {
@ -111,31 +118,29 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei
/* add the video streams using the default format codecs
and initialize the codecs */
ost = nullptr;
if ( codec_id != AV_CODEC_ID_NONE ) {
if (codec_id != AV_CODEC_ID_NONE) {
codec = avcodec_find_encoder(codec_id);
if ( !codec ) {
Fatal("Could not find encoder for '%s'", avcodec_get_name(codec_id));
if (!codec) {
Error("Could not find encoder for '%s'", avcodec_get_name(codec_id));
return -1;
}
Debug(1, "Found encoder for '%s'", avcodec_get_name(codec_id));
ost = avformat_new_stream( ofc, codec );
if ( !ost ) {
Fatal( "Could not alloc stream" );
return;
ost = avformat_new_stream(ofc, codec);
if (!ost) {
Error("Could not alloc stream");
return -1;
}
Debug( 1, "Allocated stream (%d) !=? (%d)", ost->id , ofc->nb_streams - 1 );
Debug(1, "Allocated stream (%d) !=? (%d)", ost->id , ofc->nb_streams - 1);
ost->id = ofc->nb_streams - 1;
codec_context = avcodec_alloc_context3(nullptr);
//avcodec_parameters_to_context(codec_context, ost->codecpar);
codec_context->codec_id = codec->id;
codec_context->codec_type = codec->type;
codec_context->pix_fmt = strcmp("mjpeg", ofc->oformat->name) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P;
if ( bitrate <= 100 ) {
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.
codec_context->flags |= AV_CODEC_FLAG_QSCALE;
@ -155,22 +160,22 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei
codec_context->time_base.num = 1;
ost->time_base.den = frame_rate;
ost->time_base.num = 1;
Debug( 1, "Will encode in %d fps. %dx%d", codec_context->time_base.den, width, height );
/* emit one intra frame every second */
codec_context->gop_size = frame_rate;
// some formats want stream headers to be separate
if ( of->flags & AVFMT_GLOBALHEADER )
if (of->flags & AVFMT_GLOBALHEADER)
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
avcodec_parameters_from_context(ost->codecpar, codec_context);
zm_dump_codecpar(ost->codecpar);
} else {
Fatal( "of->video_codec == AV_CODEC_ID_NONE" );
}
Error("of->video_codec == AV_CODEC_ID_NONE");
return -1;
}
return 0;
}
void VideoStream::SetParameters( ) {
@ -198,11 +203,11 @@ const char *VideoStream::MimeType() const {
bool VideoStream::OpenStream( ) {
int ret;
/* now that all the parameters are set, we can open the
/* now that all the parameters are set, we can open the
video codecs and allocate the necessary encode buffers */
if ( ost ) {
Debug(1,"Opening codec");
/* open the codec */
if ((ret = avcodec_open2(codec_context, codec, nullptr)) < 0) {
@ -319,7 +324,7 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi
if ( !initialised ) {
Initialise( );
}
if ( format ) {
int length = strlen(format);
codec_and_format = new char[length+1];;
@ -337,13 +342,13 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi
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, nullptr ) != 0 ) {
Fatal("pthread_mutex_init failed");
@ -353,35 +358,35 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi
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 != nullptr ) {
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( codec_context );
@ -409,7 +414,7 @@ VideoStream::~VideoStream( ) {
/* free the stream */
av_free( ofc );
/* free format and codec_name data. */
if ( codec_and_format ) {
delete codec_and_format;
@ -420,12 +425,12 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a
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 ) {
@ -435,35 +440,34 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a
}
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, nullptr, 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 ) {
if ( codec_context->pix_fmt != pf ) {
static struct SwsContext *img_convert_ctx = nullptr;
static struct SwsContext *img_convert_ctx = nullptr;
memcpy( tmp_opicture->data[0], buffer, buffer_size );
if ( !img_convert_ctx ) {
img_convert_ctx = sws_getCachedContext( nullptr, codec_context->width, codec_context->height, pf, codec_context->width, codec_context->height, codec_context->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr );
@ -475,37 +479,36 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size,
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 (codec_context->codec_type == AVMEDIA_TYPE_VIDEO &&
codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) {
pkt->flags |= AV_PKT_FLAG_KEY;
pkt->stream_index = ost->index;
pkt->data = (uint8_t *)opicture_ptr;
pkt->size = sizeof (AVPicture);
got_packet = 1;
int got_packet = 0;
if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO &&
codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) {
pkt->flags |= AV_PKT_FLAG_KEY;
pkt->stream_index = ost->index;
pkt->data = (uint8_t *)opicture_ptr;
pkt->size = sizeof (AVPicture);
got_packet = 1;
} else {
opicture_ptr->pts = codec_context->frame_number;
opicture_ptr->quality = codec_context->global_quality;
avcodec_send_frame(codec_context, opicture_ptr);
int ret = avcodec_receive_packet(codec_context, pkt);
if ( ret < 0 ) {
if ( AVERROR_EOF != ret ) {
Error("ERror encoding video (%d) (%s)", ret,
av_err2str(ret));
}
} else {
got_packet = 1;
avcodec_send_frame(codec_context, opicture_ptr);
int ret = avcodec_receive_packet(codec_context, pkt);
if (ret < 0) {
if (AVERROR_EOF != ret) {
Error("ERror encoding video (%d) (%s)", ret, av_err2str(ret));
}
} else {
got_packet = 1;
}
if ( got_packet ) {
// if ( c->coded_frame->key_frame )
// {
// pkt->flags |= AV_PKT_FLAG_KEY;
// }
if (got_packet) {
// if ( c->coded_frame->key_frame )
// {
// pkt->flags |= AV_PKT_FLAG_KEY;
// }
if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) {
pkt->pts = av_rescale_q( pkt->pts, codec_context->time_base, ost->time_base );
@ -517,18 +520,17 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size,
pkt->stream_index = ost->index;
}
}
return ( opicture_ptr->pts);
return opicture_ptr->pts;
}
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 ) );
}
av_packet_unref( packet );
return ret;
int ret = av_write_frame(ofc, packet);
if (ret < 0) {
Error("Error %d while writing video frame: %s", ret, av_err2str(errno));
}
av_packet_unref(packet);
return ret;
}
void *VideoStream::StreamingThreadCallback(void *ctx) {

View File

@ -68,7 +68,7 @@ protected:
static void Initialise();
void SetupFormat( );
void SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate );
int SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate );
void SetParameters();
void ActuallyOpenStream();
double ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp=false, unsigned int timestamp=0 );

View File

@ -57,6 +57,7 @@ class ZMPacket {
int64_t pts; // pts in the packet can be in another time base. This MUST be in AV_TIME_BASE_Q
bool decoded;
std::vector<ZoneStats> zone_stats;
std::string alarm_cause;
public:
AVPacket *av_packet() { return &packet; }

View File

@ -116,15 +116,16 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
, max_video_packet_count);
for (
auto it = ++pktQueue.begin();
it != pktQueue.end() and *it != add_packet;
auto it = ++pktQueue.begin();
//it != pktQueue.end() and // can't git end because we added our packet
*it != add_packet;
// iterator is incremented by erase
) {
std::shared_ptr <ZMPacket>zm_packet = *it;
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) {
Debug(1, "Found locked packet when trying to free up video packets. Skipping to next one");
delete lp;
ZMLockedPacket lp(zm_packet);
if (!lp.trylock()) {
Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up.");
++it;
continue;
}
@ -136,7 +137,7 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
) {
auto iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ((*iterator_it!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) {
if (*(*iterator_it) == zm_packet) {
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterator_it);
}
@ -153,8 +154,6 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
max_video_packet_count,
pktQueue.size());
delete lp;
if (zm_packet->packet.stream_index == video_stream_id)
break;
} // end while
@ -162,7 +161,7 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
} // end lock scope
// We signal on every packet because someday we may analyze sound
Debug(4, "packetqueue queuepacket, unlocked signalling");
condition.notify_all();
condition.notify_one();
return true;
} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet)
@ -312,7 +311,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
pktQueue.size());
pktQueue.pop_front();
packet_counts[zm_packet->packet.stream_index] -= 1;
//delete zm_packet;
}
} // end if have at least max_video_packet_count video packets remaining
// We signal on every packet because someday we may analyze sound

32
src/zm_poll_thread.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "zm_poll_thread.h"
#include "zm_monitor.h"
#include "zm_signal.h"
#include "zm_time.h"
PollThread::PollThread(Monitor *monitor) :
monitor_(monitor), terminate_(false) {
thread_ = std::thread(&PollThread::Run, this);
}
PollThread::~PollThread() {
Stop();
}
void PollThread::Start() {
if (thread_.joinable()) thread_.join();
terminate_ = false;
Debug(3, "Starting polling thread");
thread_ = std::thread(&PollThread::Run, this);
}
void PollThread::Stop() {
terminate_ = true;
if (thread_.joinable()) {
thread_.join();
}
}
void PollThread::Run() {
while (!(terminate_ or zm_terminate)) {
monitor_->Poll();
}
}

29
src/zm_poll_thread.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef ZM_POLL_THREAD_H
#define ZM_POLL_THREAD_H
#include <atomic>
#include <memory>
#include <thread>
class Monitor;
class PollThread {
public:
explicit PollThread(Monitor *monitor);
~PollThread();
PollThread(PollThread &rhs) = delete;
PollThread(PollThread &&rhs) = delete;
void Start();
void Stop();
bool Stopped() const { return terminate_; }
private:
void Run();
Monitor *monitor_;
std::atomic<bool> terminate_;
std::thread thread_;
};
#endif

View File

@ -22,6 +22,7 @@
#include "zm_config.h"
#include "zm_monitor.h"
#include "zm_packet.h"
#include "zm_signal.h"
RemoteCameraRtsp::RemoteCameraRtsp(
const Monitor *monitor,
@ -126,8 +127,8 @@ int RemoteCameraRtsp::Disconnect() {
int RemoteCameraRtsp::PrimeCapture() {
Debug(2, "Waiting for sources");
for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) {
std::this_thread::sleep_for(Microseconds(100));
for (int i = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) {
std::this_thread::sleep_for(Microseconds(10000));
}
if (!rtspThread->hasSources()) {
@ -168,8 +169,10 @@ int RemoteCameraRtsp::PrimeCapture() {
}
} // end foreach stream
if ( mVideoStreamId == -1 )
Fatal("Unable to locate video stream");
if ( mVideoStreamId == -1 ) {
Error("Unable to locate video stream");
return -1;
}
if ( mAudioStreamId == -1 )
Debug(3, "Unable to locate audio stream");
@ -179,17 +182,22 @@ int RemoteCameraRtsp::PrimeCapture() {
// Find the decoder for the video stream
AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id);
if ( codec == nullptr )
Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id);
if ( codec == nullptr ) {
Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id);
return -1;
}
// Open codec
if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 )
Panic("Can't open codec");
if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) {
Error("Can't open codec");
return -1;
}
int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
if ( (unsigned int)pSize != imagesize ) {
Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
return -1;
}
return 1;
@ -208,18 +216,13 @@ int RemoteCameraRtsp::PreCapture() {
int RemoteCameraRtsp::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
int frameComplete = false;
AVPacket *packet = &zm_packet->packet;
if ( !zm_packet->image ) {
Debug(1, "Allocating image %dx%d %d colours %d", width, height, colours, subpixelorder);
zm_packet->image = new Image(width, height, colours, subpixelorder);
}
while (!frameComplete) {
buffer.clear();
if (!rtspThread || rtspThread->IsStopped())
if (!rtspThread || rtspThread->IsStopped() || zm_terminate)
return -1;
if ( rtspThread->getFrame(buffer) ) {
if (rtspThread->getFrame(buffer)) {
Debug(3, "Read frame %d bytes", buffer.size());
Hexdump(4, buffer.head(), 16);
@ -254,36 +257,20 @@ int RemoteCameraRtsp::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
//while ( (!frameComplete) && (buffer.size() > 0) ) {
if ( buffer.size() > 0 ) {
packet->data = buffer.head();
packet->data = (uint8_t*)av_malloc(buffer.size());
memcpy(packet->data, buffer.head(), buffer.size());
//packet->data = buffer.head();
packet->size = buffer.size();
bytes += packet->size;
buffer -= packet->size;
struct timeval now;
gettimeofday(&now, NULL);
gettimeofday(&now, nullptr);
packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec;
int bytes_consumed = zm_packet->decode(mVideoCodecContext);
if ( bytes_consumed < 0 ) {
Error("Error while decoding frame %d", frameCount);
//Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size());
}
buffer -= packet->size;
if ( bytes_consumed ) {
zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode");
if (!mVideoStream->codecpar->width) {
zm_dump_codec(mVideoCodecContext);
zm_dump_codecpar(mVideoStream->codecpar);
mVideoStream->codecpar->width = zm_packet->in_frame->width;
mVideoStream->codecpar->height = zm_packet->in_frame->height;
zm_dump_codecpar(mVideoStream->codecpar);
}
zm_packet->codec_type = mVideoCodecContext->codec_type;
zm_packet->stream = mVideoStream;
frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, bytes_consumed, buffer.size());
packet->data = nullptr;
packet->size = 0;
}
zm_packet->codec_type = mVideoCodecContext->codec_type;
zm_packet->stream = mVideoStream;
frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size());
}
} /* getFrame() */
} // end while true

View File

@ -118,41 +118,34 @@ constexpr Rgb kRGBTransparent = 0x01000000;
/* Convert RGB colour value into BGR\ARGB\ABGR */
inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) {
Rgb result;
Rgb result = 0;
switch(p_subpixorder) {
switch (p_subpixorder) {
case ZM_SUBPIX_ORDER_BGR:
case ZM_SUBPIX_ORDER_BGRA:
{
BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col);
}
break;
BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col);
break;
case ZM_SUBPIX_ORDER_ARGB:
{
BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col);
}
break;
BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col);
break;
case ZM_SUBPIX_ORDER_ABGR:
{
BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col);
}
break;
/* Grayscale */
BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col);
break;
/* Grayscale */
case ZM_SUBPIX_ORDER_NONE:
result = p_col & 0xff;
break;
result = p_col & 0xff;
break;
default:
return p_col;
break;
result = p_col;
break;
}
return result;
}

View File

@ -277,7 +277,7 @@ void RtpCtrlThread::Run() {
TimePoint last_receive = std::chrono::steady_clock::now();
bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true.
while (!mTerminate && select.wait() >= 0) {
while (!mTerminate && (select.wait() >= 0)) {
TimePoint now = std::chrono::steady_clock::now();
zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) {

View File

@ -45,8 +45,10 @@ RtpSource::RtpSource(
mFrame(65536),
mFrameCount(0),
mFrameGood(true),
prevM(false),
mFrameReady(false),
mFrameProcessed(false)
mFrameProcessed(false),
mTerminate(false)
{
char hostname[256] = "";
gethostname(hostname, sizeof(hostname));

View File

@ -91,8 +91,6 @@ private:
bool mFrameGood;
bool prevM;
bool mTerminate;
bool mFrameReady;
std::condition_variable mFrameReadyCv;
std::mutex mFrameReadyMutex;
@ -100,6 +98,7 @@ private:
bool mFrameProcessed;
std::condition_variable mFrameProcessedCv;
std::mutex mFrameProcessedMutex;
bool mTerminate;
private:
void init(uint16_t seq);

View File

@ -298,6 +298,14 @@ int main(int argc, char *argv[]) {
session->GetMediaSessionId(), xop::channel_1, audioFifoPath);
audioSource->setFrequency(monitor->GetAudioFrequency());
audioSource->setChannels(monitor->GetAudioChannels());
} else if (std::string::npos != audioFifoPath.find("pcm_alaw")) {
Debug(1, "Adding G711A source at %dHz %d channels",
monitor->GetAudioFrequency(), monitor->GetAudioChannels());
session->AddSource(xop::channel_1, xop::G711ASource::CreateNew());
audioSource = new ADTS_ZoneMinderFifoSource(rtspServer,
session->GetMediaSessionId(), xop::channel_1, audioFifoPath);
audioSource->setFrequency(monitor->GetAudioFrequency());
audioSource->setChannels(monitor->GetAudioChannels());
} else {
Warning("Unknown format in %s", audioFifoPath.c_str());
}

View File

@ -109,7 +109,7 @@ void ZoneMinderFifoSource::WriteRun() {
fuNal.buffer()[2] = fuNal.buffer()[2]&~0x80; // FU header (no S bit)
headerSize = 3;
}
while (nalRemaining) {
while (nalRemaining && !stop_) {
if ( nalRemaining < maxNalSize ) {
// This is the last fragment:
fuNal.buffer()[headerSize-1] |= 0x40; // set the E bit in the FU header
@ -166,7 +166,7 @@ int ZoneMinderFifoSource::getNextFrame() {
}
Debug(3, "%s bytes read %d bytes, buffer size %u", m_fifo.c_str(), bytes_read, m_buffer.size());
while (m_buffer.size()) {
while (m_buffer.size() and !stop_) {
unsigned int data_size = 0;
int64_t pts;
unsigned char *header_end = nullptr;
@ -224,7 +224,7 @@ int ZoneMinderFifoSource::getNextFrame() {
int bytes_needed = data_size - (m_buffer.size() - header_size);
if (bytes_needed > 0) {
Debug(4, "Need another %d bytes. Trying to read them", bytes_needed);
while (bytes_needed) {
while (bytes_needed and !stop_) {
bytes_read = m_buffer.read_into(m_fd, bytes_needed);
if (bytes_read <= 0) {
Debug(1, "Failed to read another %d bytes, got %d.", bytes_needed, bytes_read);
@ -252,13 +252,14 @@ int ZoneMinderFifoSource::getNextFrame() {
{
std::unique_lock<std::mutex> lck(mutex_);
Debug(3, "have lock");
while (framesList.size()) {
while (!stop_ && framesList.size()) {
std::pair<unsigned char*, size_t> nal = framesList.front();
framesList.pop_front();
NAL_Frame *Nal = new NAL_Frame(nal.first, nal.second, pts);
m_nalQueue.push(Nal);
}
}
Debug(3, "notifying");
condition_.notify_all();
} // end while m_buffer.size()
return 1;

View File

@ -21,7 +21,7 @@ class ZoneMinderDeviceSource;
class BaseServerMediaSubsession {
public:
BaseServerMediaSubsession(StreamReplicator* replicator):
explicit BaseServerMediaSubsession(StreamReplicator* replicator):
m_replicator(replicator) {};
FramedSource* createSource(

View File

@ -3,34 +3,44 @@
#ifdef HAVE_SENDFILE4_SUPPORT
#include <sys/sendfile.h>
int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) {
int err;
err = sendfile(out_fd, in_fd, offset, size);
if ( err < 0 )
return -errno;
return err;
}
#elif HAVE_SENDFILE7_SUPPORT
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) {
int err;
err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0);
if (err && errno != EAGAIN)
return -errno;
if (size) {
*offset += size;
return size;
}
return -EAGAIN;
}
#else
#error "Your platform does not support sendfile. Sorry."
#include <unistd.h>
#endif
/* Function to send the contents of a file. Will use sendfile or fall back to reading/writing */
ssize_t zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) {
#ifdef HAVE_SENDFILE4_SUPPORT
ssize_t err = sendfile(out_fd, in_fd, offset, size);
if (err < 0) {
return -errno;
}
return err;
#elif HAVE_SENDFILE7_SUPPORT
ssize_t err = sendfile(in_fd, out_fd, *offset, size, nullptr, &size, 0);
if (err && errno != EAGAIN)
return -errno;
return size;
#else
uint8_t buffer[size];
ssize_t err = read(in_fd, buffer, size);
if (err < 0) {
Error("Unable to read %zu bytes: %s", size, strerror(errno));
return -errno;
}
err = fwrite(out_fd, buffer, size);
if (err < 0) {
Error("Unable to write %zu bytes: %s", size, strerror(errno));
return -errno;
}
return err;
#endif
}
#endif // ZM_SENDFILE_H

View File

@ -31,10 +31,8 @@ constexpr Seconds StreamBase::MAX_STREAM_DELAY;
constexpr Milliseconds StreamBase::MAX_SLEEP;
StreamBase::~StreamBase() {
if (vid_stream) {
delete vid_stream;
vid_stream = nullptr;
}
delete vid_stream;
delete temp_img_buffer;
closeComms();
}
@ -128,10 +126,10 @@ bool StreamBase::checkCommandQueue() {
return true;
}
} else if ( connkey ) {
Warning("No sd in checkCommandQueue, comms not open?");
Warning("No sd in checkCommandQueue, comms not open for connkey %06d?", connkey);
} else {
// Perfectly valid if only getting a snapshot
Debug(1, "No sd in checkCommandQueue, comms not open?");
Debug(1, "No sd in checkCommandQueue, comms not open.");
}
return false;
} // end bool StreamBase::checkCommandQueue()
@ -157,7 +155,6 @@ Image *StreamBase::prepareImage(Image *image) {
int disp_image_width = (image->Width() * scale) / ZM_SCALE_BASE, disp_image_height = (image->Height() * scale) / ZM_SCALE_BASE;
int last_disp_image_width = (image->Width() * last_scale) / ZM_SCALE_BASE, last_disp_image_height = (image->Height() * last_scale) / ZM_SCALE_BASE;
int send_image_width = (disp_image_width * act_mag ) / mag, send_image_height = (disp_image_height * act_mag ) / mag;
int last_send_image_width = (last_disp_image_width * last_act_mag ) / last_mag, last_send_image_height = (last_disp_image_height * last_act_mag ) / last_mag;
Debug(3,
"Scaling by %d, zooming by %d = magnifying by %d(%d)\n"
@ -169,8 +166,7 @@ Image *StreamBase::prepareImage(Image *image) {
"Last actual image width = %d, height = %d\n"
"Display image width = %d, height = %d\n"
"Last display image width = %d, height = %d\n"
"Send image width = %d, height = %d\n"
"Last send image width = %d, height = %d\n",
"Send image width = %d, height = %d\n",
scale, zoom, mag, act_mag,
last_scale, last_zoom, last_mag, last_act_mag,
base_image_width, base_image_height,
@ -180,8 +176,7 @@ Image *StreamBase::prepareImage(Image *image) {
last_act_image_width, last_act_image_height,
disp_image_width, disp_image_height,
last_disp_image_width, last_disp_image_height,
send_image_width, send_image_height,
last_send_image_width, last_send_image_height
send_image_width, send_image_height
);
if ( ( mag != ZM_SCALE_BASE ) && (act_mag != ZM_SCALE_BASE) ) {
@ -386,9 +381,9 @@ void StreamBase::openComms() {
strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path));
rem_addr.sun_family = AF_UNIX;
last_comm_update = std::chrono::system_clock::now();
last_comm_update = std::chrono::steady_clock::now();
Debug(3, "comms open at %s", loc_sock_path);
} // end if connKey > 0
Debug(3, "comms open at %s", loc_sock_path);
} // end void StreamBase::openComms()
void StreamBase::closeComms() {

View File

@ -40,6 +40,7 @@ public:
STREAM_SINGLE,
STREAM_MPEG
} StreamType;
typedef enum { FRAME_NORMAL, FRAME_ANALYSIS } FrameType;
protected:
static constexpr Seconds MAX_STREAM_DELAY = Seconds(5);
@ -88,6 +89,9 @@ protected:
CMD_VARPLAY,
CMD_GET_IMAGE,
CMD_QUIT,
CMD_MAXFPS,
CMD_ANALYZE_ON,
CMD_ANALYZE_OFF,
CMD_QUERY=99
} MsgCommand;
@ -96,6 +100,7 @@ protected:
std::shared_ptr<Monitor> monitor;
StreamType type;
FrameType frame_type;
const char *format;
int replay_rate;
int scale;
@ -118,26 +123,30 @@ protected:
bool paused;
int step;
SystemTimePoint now;
SystemTimePoint last_comm_update;
TimePoint now;
TimePoint last_comm_update;
double maxfps;
double base_fps; // Should be capturing fps, hence a rough target
double effective_fps; // Target fps after taking max_fps into account
double actual_fps; // sliding calculated actual streaming fps achieved
SystemTimePoint last_fps_update;
TimePoint last_fps_update;
int frame_count; // Count of frames sent
int last_frame_count; // Used in calculating actual_fps from frame_count - last_frame_count
int frame_mod;
SystemTimePoint last_frame_sent;
TimePoint last_frame_sent;
SystemTimePoint last_frame_timestamp;
TimePoint when_to_send_next_frame; // When to send next frame so if now < send_next_frame, skip
VideoStream *vid_stream;
CmdMsg msg;
unsigned char *temp_img_buffer; // Used when encoding or sending file data
size_t temp_img_buffer_size;
protected:
bool loadMonitor(int monitor_id);
bool checkInitialised();
@ -151,6 +160,7 @@ public:
monitor_id(0),
monitor(nullptr),
type(DEFAULT_TYPE),
frame_type(FRAME_NORMAL),
format(""),
replay_rate(DEFAULT_RATE),
scale(DEFAULT_SCALE),
@ -175,7 +185,9 @@ public:
actual_fps(0.0),
frame_count(0),
last_frame_count(0),
frame_mod(1)
frame_mod(1),
temp_img_buffer(nullptr),
temp_img_buffer_size(0)
{
memset(&loc_sock_path, 0, sizeof(loc_sock_path));
memset(&loc_addr, 0, sizeof(loc_addr));
@ -196,7 +208,9 @@ public:
type = STREAM_RAW;
}
#endif
}
void setStreamFrameType(FrameType p_type) {
frame_type = p_type;
}
void setStreamFormat(const char *p_format) {
format = p_format;
@ -207,10 +221,11 @@ public:
scale = DEFAULT_SCALE;
}
void setStreamReplayRate(int p_rate) {
Debug(2, "Setting replay_rate %d", p_rate);
Debug(1, "Setting replay_rate %d", p_rate);
replay_rate = p_rate;
}
void setStreamMaxFPS(double p_maxfps) {
Debug(1, "Setting max fps to %f", p_maxfps);
maxfps = p_maxfps;
}
void setStreamBitrate(int p_bitrate) {

View File

@ -22,7 +22,14 @@
#include "zm_image.h"
#include "zm_logger.h"
SWScale::SWScale() : gotdefaults(false), swscale_ctx(nullptr), input_avframe(nullptr), output_avframe(nullptr) {
SWScale::SWScale() :
gotdefaults(false),
swscale_ctx(nullptr),
input_avframe(nullptr),
output_avframe(nullptr),
default_width(0),
default_height(0)
{
Debug(4, "SWScale object created");
}

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